/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.knox.gateway.util;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import org.junit.Assert;
import org.junit.Test;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;

public class JsonPathTest {

  @Test
  public void testCompileInvalidExpressions() {
    String[] paths = {
        null,
        "",
        "$.",
        "$[",
        "$[]",
        ".",
        "[",
        "$.[one]",
        "$.['one']",
        "(",
        ")",
        "@",
        "$?",
        "$(",
        "$)",
        "$@",
        "$?"
    };

    for( String path : paths ) {
      try {
        JsonPath.compile( path );
        fail( "Expected IllegalArgumentException" );
      } catch( IllegalArgumentException e ) {
        // Expected exception
        assertNotNull(e);
      }
    }
  }

  @Test
  public void testCompileUnsupportedExpressions() {
    String[] paths = {
        "$..book[-1:]",
        "$..book[,1]",
        "$..book[0,1]",
        "$..book[:2]",
        "$..book[0:7:2]",
        "$..book[(@.length-1)]",
        "$..book[?(@.isbn)]",
        "$..book[?(@.price<10)]"
    };

    for( String path : paths ) {
      try {
        JsonPath.compile( path );
        fail( "Expected IllegalArgumentException for " + path );
      } catch( IllegalArgumentException e ) {
        // Expected.
        assertNotNull(e);
      }
    }
  }

  @Test
  public void testCompileValidExpressions() {

    JsonPath.Expression expression;
    JsonPath.Segment[] segments;

    expression = JsonPath.compile( "$" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 1 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[0].getField(), nullValue() );
    assertThat( segments[0].getIndex(), is( -1 ) );

    expression = JsonPath.compile( "$.one" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[0].getField(), nullValue() );
    assertThat( segments[1].getField(), is( "one" ) );

    expression = JsonPath.compile( "$.'one'" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[0].getField(), nullValue() );
    assertThat( segments[1].getField(), is( "one" ) );

    expression = JsonPath.compile( "$[one]" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[0].getField(), nullValue() );
    assertThat( segments[1].getField(), is( "one" ) );

    expression = JsonPath.compile( "$['one']" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[0].getField(), nullValue() );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "one" ) );
    assertThat( segments[1].getIndex(), is( -1 ) );

    expression = JsonPath.compile( "$[7]" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[0].getField(), nullValue() );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.INDEX ) );
    assertThat( segments[1].getField(), nullValue() );
    assertThat( segments[1].getIndex(), is( 7 ) ) ;

    expression = JsonPath.compile( "$.7" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[0].getField(), nullValue() );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.INDEX ) );
    assertThat( segments[1].getField(), nullValue() );
    assertThat( segments[1].getIndex(), is( 7 ) ) ;

    expression = JsonPath.compile( "$['7']" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[0].getField(), nullValue() );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "7" ) );
    assertThat( segments[1].getIndex(), is( -1 ) ) ;

    expression = JsonPath.compile( "$.'7'" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[0].getField(), nullValue() );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "7" ) );
    assertThat( segments[1].getIndex(), is( -1 ) ) ;

    expression = JsonPath.compile( "$.*.field" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 3 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.WILD ) );
    assertThat( segments[1].getField(), nullValue() );
    assertThat( segments[2].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[2].getField(), is( "field" ) );

    expression = JsonPath.compile( "$.**.field" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 3 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.GLOB ) );
    assertThat( segments[1].getField(), is( nullValue() ) );
    assertThat( segments[2].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[2].getField(), is( "field" ) );

    expression = JsonPath.compile( "$[*][field]" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 3 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.WILD ) );
    assertThat( segments[1].getField(), nullValue() );
    assertThat( segments[2].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[2].getField(), is( "field" ) );

    expression = JsonPath.compile( "$[**]['field']" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 3 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.GLOB ) );
    assertThat( segments[1].getField(), nullValue() );
    assertThat( segments[2].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[2].getField(), is( "field" ) );

    expression = JsonPath.compile( "$..field" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 3 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.GLOB ) );
    assertThat( segments[1].getField(), nullValue() );
    assertThat( segments[2].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[2].getField(), is( "field" ) );

    expression = JsonPath.compile( "$[one].two" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 3 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "one" ) );
    assertThat( segments[2].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[2].getField(), is( "two" ) );

    expression = JsonPath.compile( "$.one[two]" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 3 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "one") );
    assertThat( segments[2].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[2].getField(), is( "two" ) );

    expression = JsonPath.compile( "$..*" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 3 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[0].getField(), nullValue() );
    assertThat( segments[0].getIndex(), is( -1 ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.GLOB ) );
    assertThat( segments[1].getField(), nullValue() );
    assertThat( segments[1].getIndex(), is( -1 ) );
    assertThat( segments[2].getType(), is( JsonPath.Segment.Type.WILD ) );
    assertThat( segments[2].getField(), nullValue() );
    assertThat( segments[2].getIndex(), is( -1 ) );
  }

  @Test
  public void testCompileQuotedExpressions() {
    JsonPath.Expression expression;
    JsonPath.Segment[] segments;

    expression = JsonPath.compile( "$.fieldName" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "fieldName" ) );

    expression = JsonPath.compile( "$[fieldName]" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "fieldName" ) );

    expression = JsonPath.compile( "$['fieldName']" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "fieldName" ) );

    expression = JsonPath.compile( "$.field_name" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "field_name" ) );

    expression = JsonPath.compile( "$['field_name']" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "field_name" ) );

    expression = JsonPath.compile( "$[field_name]" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "field_name" ) );

    expression = JsonPath.compile( "$['field-name']" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "field-name" ) );

    expression = JsonPath.compile( "$[field-name]" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "field-name" ) );

    expression = JsonPath.compile( "$.field-name" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "field-name" ) );

    expression = JsonPath.compile( "$['field.name']" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "field.name" ) );

    expression = JsonPath.compile( "$[field.name]" );
    assertThat( expression, notNullValue() );
    segments = expression.getSegments();
    assertThat( segments, notNullValue() );
    assertThat( segments.length, is( 2 ) );
    assertThat( segments[0].getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( segments[1].getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( segments[1].getField(), is( "field.name" ) );

  }

  @Test
  public void testEvaluateObjects() throws IOException {
    String json;
    JsonPath.Segment seg;
    List<JsonPath.Match> matches;
    JsonPath.Match match;
    JsonPath.Match parent;
    JsonNode root;
    JsonNode node;
    JsonPath.Expression expression;

    JsonFactory factory = new JsonFactory();
    ObjectMapper mapper = new ObjectMapper( factory );


    json = "{ \"field\" : \"value\" }";
    root = mapper.readTree( json );
    assertThat( root, notNullValue() );

    expression = JsonPath.compile( "$.field" );
    matches = expression.evaluate( root );
    assertThat( matches, notNullValue() );
    assertThat( matches.size(), is( 1 ) );
    match = matches.get( 0 );
    assertThat( matches,  notNullValue() );
    seg = matches.get( 0 ).getSegment();
    assertThat( seg, notNullValue() );
    assertThat( seg.getType(), is( JsonPath.Segment.Type.FIELD ) );
    assertThat( seg.getField(), is( "field" ) );
    node = match.getNode();
    assertThat( node, notNullValue() );
    assertThat( node.getNodeType(), is( JsonNodeType.STRING ) );
    assertThat( node.asText(), is( "value" ) );
    parent = match.getParent();
    assertThat( parent, notNullValue() );
    assertThat( parent.getNode(), sameInstance( root ) );
    assertThat( parent.getParent(), nullValue() );
    assertThat( parent.getSegment().getType(), is( JsonPath.Segment.Type.ROOT ) );


    json = "{ \"outer\" : { \"inner\" : \"value\"}  }";
    root = mapper.readTree( json );
    assertThat( root, notNullValue() );

    expression = JsonPath.compile( "$.outer.inner" );
    matches = expression.evaluate( root );
    match = matches.get( 0 );
    assertThat( match, notNullValue() );
    assertThat( match.getField(), is( "inner" ) );
    seg = match.getSegment();
    assertThat( seg, notNullValue() );
    assertThat( seg.getField(), is( "inner" ) );
    assertThat( seg.getType(), is( JsonPath.Segment.Type.FIELD ) );

    node = match.getNode();
    assertThat( node, notNullValue() );
    assertThat( node.asText(), is( "value" ) );

    parent = match.getParent();
    assertThat( parent, notNullValue() );
    assertThat( parent.getField(), is( "outer") );
    assertThat( parent.getNode().getNodeType(), is( JsonNodeType.OBJECT ) );

    parent = parent.getParent();
    assertThat( parent.getSegment().getType(), is( JsonPath.Segment.Type.ROOT ) );
    assertThat( parent.getNode().getNodeType(), is( JsonNodeType.OBJECT ) );


    json = "{ \"outer\" : { \"inner\" : \"value\"}  }";
    root = mapper.readTree( json );
    assertThat( root, notNullValue() );

    expression = JsonPath.compile( "$.*.inner" );
    matches = expression.evaluate( root );
    assertThat( matches.size(), is( 1 ) ) ;
    match = matches.get( 0 );
    assertThat( match.getField(), is( "inner" ) );
    assertThat( match.getNode().asText(), is( "value" ) );
  }

  @Test
  public void testEvaluateArrays() throws IOException {
    String json;
    List<JsonPath.Match> matches;
    JsonPath.Match match;
    JsonNode root;
    JsonPath.Expression expression;

    JsonFactory factory = new JsonFactory();
    ObjectMapper mapper = new ObjectMapper( factory );

    json = "[ \"outer-array\", { \"nested-field\" : \"nested-object\" }, [ \"nested-array\" ] ]";
    root = mapper.readTree( json );
    assertThat( root, notNullValue() );

    expression = JsonPath.compile( "$[0]" );
    matches = expression.evaluate( root );
    assertThat( matches.size(), is( 1 ) ) ;
    match = matches.get( 0 );
    assertThat( match.getIndex(), is( 0 ) );
    assertThat( match.getSegment().getIndex(), is( 0 ) );
    assertThat( match.getNode().asText(), is( "outer-array" ) );

    expression = JsonPath.compile( "$[1][nested-field]" );
    matches = expression.evaluate( root );
    assertThat( matches.size(), is( 1 ) ) ;
    match = matches.get( 0 );
    assertThat( match.getField(), is( "nested-field" ) );
    assertThat( match.getNode().asText(), is( "nested-object" ) );

    expression = JsonPath.compile( "$[*][nested-field]" );
    matches = expression.evaluate( root );
    assertThat( matches.size(), is( 1 ) ) ;
    match = matches.get( 0 );
    assertThat( match.getField(), is( "nested-field" ) );
    assertThat( match.getNode().asText(), is( "nested-object" ) );

    json = "{ \"array\" : [ { \"name\" : \"value-1\" }, { \"name\" : \"value-2 \"}, { \"name\" : \"value-3\" } ] }";
    root = mapper.readTree( json );
    expression = JsonPath.compile( "$.array.*.name" );
    matches = expression.evaluate( root );
    assertThat( matches.size(), is( 3 ) ) ;
    expression = JsonPath.compile( "$.array[*].name" );
    matches = expression.evaluate( root );
    assertThat( matches.size(), is( 3 ) ) ;
    expression = JsonPath.compile( "$.array[*][name]" );
    matches = expression.evaluate( root );
    assertThat( matches.size(), is( 3 ) ) ;

    expression = JsonPath.compile( "$.array" );
    matches = expression.evaluate( root );
    match = matches.get( 0 );
    expression = JsonPath.compile( "$.*.name" );
    matches = expression.evaluate( match.getNode() );
    Assert.assertThat( matches.size(), is( 3 ) );
  }

  @Test
  public void testGlobMatching() throws IOException {
    String json;
    List<JsonPath.Match> matches;
    JsonNode root;
    JsonPath.Expression expression;
    Set<String> matchValues;

    JsonFactory factory = new JsonFactory();
    ObjectMapper mapper = new ObjectMapper( factory );

    json = "{ \"field\" : \"value\" }";
    root = mapper.readTree( json );
    assertThat( root, notNullValue() );

    expression = JsonPath.compile( "$..field" );
    matches = expression.evaluate( root );
    matchValues = new HashSet<>();
    assertThat( matches.size(), is( 1 ) );
    for( JsonPath.Match match : matches ) {
      matchValues.add( match.getNode().asText() );
    }
    assertThat( matchValues, hasItem( "value" ) );

    json = "{ \"field-1\" : { \"field-1-1\" : { \"field-1-1-1\" : \"value-1-1-1\", \"field\" : \"value-A\" }, \"field\" : \"value-B\"}, \"field-2\" : { \"field-2-1\" : { \"field-2-1-1\" : \"value-2-1-1\", \"field\" : \"value-C\" }, \"field\" : \"value-D\" }, \"field\" : \"value-E\" }";
    root = mapper.readTree( json );
    assertThat( root, notNullValue() );

    expression = JsonPath.compile( "$..field" );
    matches = expression.evaluate( root );
    assertThat( matches.size(), is( 5 ) );
    matchValues = new HashSet<>();
    for( JsonPath.Match match : matches ) {
      matchValues.add( match.getNode().asText() );
    }
    assertThat( matchValues, hasItem( "value-A" ) );
    assertThat( matchValues, hasItem( "value-B" ) );
    assertThat( matchValues, hasItem( "value-C" ) );
    assertThat( matchValues, hasItem( "value-D" ) );
    assertThat( matchValues, hasItem( "value-E" ) );
  }
}