Skip to content
Permalink
Browse files

Enable table data comparison by keys for http responses (#324)

  • Loading branch information...
MykolaGolubyev committed May 10, 2019
1 parent f87e43a commit 49ede74a4c201c1579af2710f08d46326c4276e5
Showing with 166 additions and 13 deletions.
  1. +1 −0 .gitignore
  2. +2 −0 webtau-core/src/main/java/com/twosigma/webtau/data/table/Record.java
  3. +2 −0 webtau-core/src/main/java/com/twosigma/webtau/data/table/TableData.java
  4. +1 −1 webtau-core/src/main/java/com/twosigma/webtau/data/table/comparison/TableDataComparison.java
  5. +6 −2 webtau-core/src/main/java/com/twosigma/webtau/data/table/{ → header}/CompositeKey.java
  6. +13 −0 .../main/java/com/twosigma/webtau/data/table/header/CompositeKeyDefaultUnderlyingValueExtractor.java
  7. +10 −0 ...ore/src/main/java/com/twosigma/webtau/data/table/header/CompositeKeyUnderlyingValueExtractor.java
  8. +26 −0 ...re/src/main/java/com/twosigma/webtau/data/table/header/CompositeKeyUnderlyingValueExtractors.java
  9. +4 −2 webtau-core/src/main/java/com/twosigma/webtau/data/table/{ → header}/Header.java
  10. +1 −1 .../java/com/twosigma/webtau/expectation/equality/handlers/IterableAndTableDataCompareToHandler.java
  11. +1 −0 webtau-core/src/test/groovy/com/twosigma/webtau/data/table/CompositeKeyTest.groovy
  12. +1 −0 webtau-core/src/test/groovy/com/twosigma/webtau/data/table/HeaderTest.groovy
  13. +1 −0 webtau-core/src/test/groovy/com/twosigma/webtau/data/table/RecordTest.groovy
  14. +7 −0 webtau-docs/webtau/REST/complex-types.md
  15. +6 −0 webtau-docs/webtau/REST/matchers.md
  16. +24 −0 webtau-feature-testing/examples/scenarios/rest/springboot/listMatchByKey.groovy
  17. +6 −0 ...-feature-testing/src/test/groovy/com/twosigma/webtau/featuretesting/WebTauRestFeaturesTest.groovy
  18. +9 −0 webtau-feature-testing/test-expectations/scenarios/rest/springboot/listMatchByKey/run-details.json
  19. +10 −0 webtau-http-groovy/src/test/groovy/com/twosigma/webtau/http/HttpGroovyTest.groovy
  20. +15 −0 webtau-http/src/main/java/com/twosigma/webtau/http/datanode/CompositeKeyDataNodeValueExtractor.java
  21. +1 −1 webtau-http/src/main/java/com/twosigma/webtau/http/datanode/DataNode.java
  22. +1 −0 ...rces/META-INF/services/com.twosigma.webtau.data.table.header.CompositeKeyUnderlyingValueExtractor
  23. +18 −6 webtau-http/src/test/java/com/twosigma/webtau/http/HttpJavaTest.java
@@ -6,6 +6,7 @@ target
node_modules
webtau.*report*.html
webtau-report-*.html
.webtau.cache.json
.vertx
webtau-http-groovy/doc-artifacts/*
webtau-feature-testing/doc-artifacts/*
@@ -18,6 +18,8 @@

import com.twosigma.webtau.data.MultiValue;
import com.twosigma.webtau.data.table.autogen.TableDataCellValueGenerator;
import com.twosigma.webtau.data.table.header.CompositeKey;
import com.twosigma.webtau.data.table.header.Header;

import java.util.*;
import java.util.function.Function;
@@ -16,6 +16,8 @@

package com.twosigma.webtau.data.table;

import com.twosigma.webtau.data.table.header.CompositeKey;
import com.twosigma.webtau.data.table.header.Header;
import com.twosigma.webtau.utils.JsonUtils;

import java.util.*;
@@ -16,7 +16,7 @@

package com.twosigma.webtau.data.table.comparison;

import com.twosigma.webtau.data.table.CompositeKey;
import com.twosigma.webtau.data.table.header.CompositeKey;
import com.twosigma.webtau.data.table.Record;
import com.twosigma.webtau.data.table.TableData;
import com.twosigma.webtau.expectation.equality.CompareToComparator;
@@ -14,7 +14,9 @@
* limitations under the License.
*/

package com.twosigma.webtau.data.table;
package com.twosigma.webtau.data.table.header;

import com.twosigma.webtau.data.table.TableData;

import java.util.Collections;
import java.util.List;
@@ -28,7 +30,9 @@
private List<Object> values;

public CompositeKey(Stream<Object> values) {
this.values = values.collect(Collectors.toList());
this.values = values
.map(CompositeKeyUnderlyingValueExtractors::extract)
.collect(Collectors.toList());
}

public List<?> getValues() {
@@ -0,0 +1,13 @@
package com.twosigma.webtau.data.table.header;

public class CompositeKeyDefaultUnderlyingValueExtractor implements CompositeKeyUnderlyingValueExtractor {
@Override
public boolean handles(Object value) {
return true;
}

@Override
public Object extract(Object value) {
return value;
}
}
@@ -0,0 +1,10 @@
package com.twosigma.webtau.data.table.header;

/**
* TableData cell values can consist of high value wrappers.
* To consistently match table keys, composite key needs to be built from the original underlying values
*/
public interface CompositeKeyUnderlyingValueExtractor {
boolean handles(Object value);
Object extract(Object value);
}
@@ -0,0 +1,26 @@
package com.twosigma.webtau.data.table.header;

import com.twosigma.webtau.utils.ServiceLoaderUtils;
import com.twosigma.webtau.utils.TraceUtils;

import java.util.List;

public class CompositeKeyUnderlyingValueExtractors {
private static final List<CompositeKeyUnderlyingValueExtractor> extractors = discover();

public static Object extract(Object value) {
return extractors.stream()
.filter(e -> e.handles(value))
.findFirst().orElseThrow(() -> new IllegalStateException(
"No CompositeKeyUnderlyingValueExtractor found for: " + TraceUtils.renderValueAndType(value)))
.extract(value);
}

private static List<CompositeKeyUnderlyingValueExtractor> discover() {
List<CompositeKeyUnderlyingValueExtractor> result =
ServiceLoaderUtils.load(CompositeKeyUnderlyingValueExtractor.class);
result.add(new CompositeKeyDefaultUnderlyingValueExtractor());

return result;
}
}
@@ -14,7 +14,9 @@
* limitations under the License.
*/

package com.twosigma.webtau.data.table;
package com.twosigma.webtau.data.table.header;

import com.twosigma.webtau.data.table.Record;

import java.util.ArrayList;
import java.util.HashMap;
@@ -124,7 +126,7 @@ public int columnIdxByName(String columnName) {
return idx;
}

void validateIdx(int idx) {
public void validateIdx(int idx) {
if (idx < 0 || idx >= namesByIndex.size()) {
throw new IllegalArgumentException("column idx " + idx + " is out of boundaries. header size is " +
namesByIndex.size() + ", header is " + namesByIndex);
@@ -17,7 +17,7 @@
package com.twosigma.webtau.expectation.equality.handlers;

import com.twosigma.webtau.data.converters.ToMapConverters;
import com.twosigma.webtau.data.table.Header;
import com.twosigma.webtau.data.table.header.Header;
import com.twosigma.webtau.data.table.TableData;
import com.twosigma.webtau.data.table.comparison.TableDataComparison;
import com.twosigma.webtau.data.table.comparison.TableDataComparisonReport;
@@ -16,6 +16,7 @@

package com.twosigma.webtau.data.table

import com.twosigma.webtau.data.table.header.CompositeKey
import org.junit.Test

class CompositeKeyTest {
@@ -16,6 +16,7 @@

package com.twosigma.webtau.data.table

import com.twosigma.webtau.data.table.header.Header
import org.junit.Test

import java.util.stream.Stream
@@ -16,6 +16,7 @@

package com.twosigma.webtau.data.table

import com.twosigma.webtau.data.table.header.Header
import org.junit.Test

import static com.twosigma.webtau.Ddjt.*
@@ -14,3 +14,10 @@ Given the response, we want to make sure there is an entry with a specified `fir
If you want to make sure that all the values in the list are what you need - use `TableData`.

:include-file: examples/scenarios/rest/springboot/listMatch.groovy

# Order Agnostic Match

Use `*key` column(s) if list order is not guaranteed

:include-file: examples/scenarios/rest/springboot/listMatchByKey.groovy

@@ -66,6 +66,12 @@ Groovy: :include-groovy: com/twosigma/webtau/http/HttpGroovyTest.groovy {entry:
Java: :include-java: com/twosigma/webtau/http/HttpJavaTest.java {entry: "equalityMatcher", bodyOnly: true, commentsType: "inline"}
```

```tabs {rightSide: true}
Groovy: :include-groovy: com/twosigma/webtau/http/HttpGroovyTest.groovy {entry: "equality matcher table keys", bodyOnly: true, commentsType: "inline"}
Java: :include-java: com/twosigma/webtau/http/HttpJavaTest.java {entry: "equalityMatcherTableKey", bodyOnly: true, commentsType: "inline"}
```


# Greater/Less/Equal

Use `greaterThan`, `greaterThanOrEqual`, `lessThan`, and `lessThanOrEqual` to assert numeric values.
@@ -0,0 +1,24 @@
package scenarios.rest.springboot

import static com.twosigma.webtau.WebTauGroovyDsl.*

scenario("list Customers and assert with a Table Data using key column") {
def id1 = createCustomer firstName: "FN1", lastName: "LN1"
def id2 = createCustomer firstName: "FN2", lastName: "LN2"
def id3 = createCustomer firstName: "FN3", lastName: "LN3"

http.get("/customers") {
_embedded.customers.should == ['*id' | 'firstName' | 'lastName'] {
_________________________________
id2 | 'FN2' | 'LN2'
id1 | 'FN1' | 'LN1'
id3 | 'FN3' | 'LN3'}
}
}

def createCustomer(Map payload) {
def id = http.post("/customers", payload) { id }
http.doc.capture('create-customer')

return id
}
@@ -114,6 +114,12 @@ class WebTauRestFeaturesTest {
runCli('springboot/listMatch.groovy', 'springboot/webtau.cfg', "--url=$customersBaseUrl")
}

@Test
void "list match by key"() {
deleteCustomers()
runCli('springboot/listMatchByKey.groovy', 'springboot/webtau.cfg', "--url=$customersBaseUrl")
}

private static void runCli(String restTestName, String configFileName, String... additionalArgs) {
testRunner.runCli("scenarios/rest/$restTestName",
"scenarios/rest/$configFileName", additionalArgs)
@@ -0,0 +1,9 @@
{
"scenarioDetails" : [ {
"scenario" : "list Customers and assert with a Table Data using key column",
"stepsSummary" : {
"numberOfSuccessful" : 9
}
} ],
"exitCode" : 0
}
@@ -777,6 +777,16 @@ class HttpGroovyTest implements HttpConfiguration {
http.doc.capture("end-point-object-equality-matchers")
}

@Test
void "equality matcher table keys"() {
http.get("/end-point") {
complexList.should == [ "*id" | "k1" | "k2"] { // order agnostic key based match
________________________
"id2" | "v11" | 40
"id1" | "v1" | 30 }
}
}

@Test
void "compare numbers with greater less matchers"() {
http.get("/end-point-numbers") {
@@ -0,0 +1,15 @@
package com.twosigma.webtau.http.datanode;

import com.twosigma.webtau.data.table.header.CompositeKeyUnderlyingValueExtractor;

public class CompositeKeyDataNodeValueExtractor implements CompositeKeyUnderlyingValueExtractor {
@Override
public boolean handles(Object value) {
return value instanceof DataNode;
}

@Override
public Object extract(Object value) {
return ((DataNode)value).get();
}
}
@@ -25,7 +25,7 @@

import static com.twosigma.webtau.Ddjt.createActualPath;

public interface DataNode extends ActualValueExpectations, DataNodeExpectations, Iterable<DataNode> {
public interface DataNode extends DataNodeExpectations, Iterable<DataNode> {
DataNodeId id();

DataNode get(String name);
@@ -0,0 +1 @@
com.twosigma.webtau.http.datanode.CompositeKeyDataNodeValueExtractor
@@ -98,14 +98,25 @@ public void equalityMatcher() {
"k1", "v1",
"k3", "v3"))); // matching only specified fields and can be nested multiple times

body.get("complexList").should(equal(table("k1" , "k2").values( // matching only specified fields, but number of entries must be exact
"v1" , 30,
"v11", 40)));
body.get("complexList").should(equal(table("k1" , "k2", // matching only specified fields, but number of entries must be exact
________________,
"v1" , 30,
"v11", 40)));
});

http.doc.capture("end-point-object-equality-matchers");
}

@Test
public void equalityMatcherTableKey() {
http.get("/end-point", (header, body) -> {
body.get("complexList").should(equal(table("*id", "k1" , "k2", // order agnostic key based match
________________,
"id2", "v11", 40,
"id1", "v1" , 30)));
});
}

@Test
public void compareNumbersWithGreaterLessMatchers() {
http.get("/end-point-numbers", (header, body) -> {
@@ -172,9 +183,10 @@ public void matchersCombo() {
"k1", notEqual("v1"), // any value but v1
"k2", greaterThanOrEqual(120))));

TableData expected = table("k1", "k2").values( // matching only specified fields, but number of entries must be exact
withNumber, lessThan(120),
"v11", greaterThan(150));
TableData expected = table("k1" , "k2", // matching only specified fields, but number of entries must be exact
________________________________,
withNumber , lessThan(120),
"v11" , greaterThan(150));

body.get("complexList").should(equal(expected));
});

0 comments on commit 49ede74

Please sign in to comment.
You can’t perform that action at this time.