/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Licensed 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.neo4j.driver.v1.tck;
import cucumber.api.DataTable;
import cucumber.api.java.en.And;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import cucumber.runtime.table.DiffableRow;
import org.hamcrest.CoreMatchers;
import org.hamcrest.core.IsInstanceOf;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.neo4j.driver.v1.Statement;
import org.neo4j.driver.v1.Value;
import org.neo4j.driver.v1.summary.InputPosition;
import org.neo4j.driver.v1.summary.Notification;
import org.neo4j.driver.v1.summary.Plan;
import org.neo4j.driver.v1.summary.ProfiledPlan;
import org.neo4j.driver.v1.summary.ResultSummary;
import org.neo4j.driver.v1.summary.StatementType;
import org.neo4j.driver.v1.summary.SummaryCounters;
import org.neo4j.driver.v1.tck.tck.util.runners.CypherStatementRunner;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.neo4j.driver.v1.tck.Environment.runners;
import static org.neo4j.driver.v1.tck.tck.util.ResultParser.getJavaValueIntAsLong;
import static org.neo4j.driver.v1.tck.tck.util.ResultParser.getJavaValueNormalInts;
public class DriverResultApiSteps
{
List<ResultSummary> summaries;
List<Statement> statements;
@When( "^the `Statement Result` is consumed a `Result Summary` is returned$" )
public void the_result_is_summerized() throws Throwable
{
summaries = new ArrayList<>();
for ( CypherStatementRunner runner : runners )
{
summaries.add( runner.result().consume() );
}
ResultSummary first = summaries.get( 0 );
for ( ResultSummary resultSummary : summaries )
{
assertThat( resultSummary, equalTo( first ) );
}
}
@Then( "^the `Statement Result` is closed$" )
public void theResultCursorIsFullyConsumed() throws Throwable
{
for ( CypherStatementRunner runner : runners )
{
assertThat( runner.result().list().isEmpty(), equalTo( true ) );
}
}
@And( "^I request a `Statement` from the `Result Summary`$" )
public void iRequestAStatementFromTheResultSummary() throws Throwable
{
statements = new ArrayList<>();
for ( ResultSummary resultSummary : summaries )
{
statements.add( resultSummary.statement() );
}
}
@Then( "^requesting the `Statement` as text should give: (.*)$" )
public void requestingTheStatementAsTextShouldGive( String expected ) throws Throwable
{
for ( Statement statement : statements )
{
assertThat( statement.text(), equalTo( expected ) );
}
}
@And( "^requesting the `Statement` parameter should give: (.*)$" )
public void requestingTheStatementAsParameterShouldGiveNull( String expected ) throws Throwable
{
for ( int i = 0; i < statements.size(); i++ )
{
assertThat( statements.get( i ).parameters(), equalTo( runners.get( i ).parameters() ) );
}
}
@Then( "^requesting `Counters` from `Result Summary` should give$" )
public void iShouldGetUpdateStatisticsContaining( DataTable expectedStatistics ) throws Throwable
{
for ( ResultSummary resultSummary : summaries )
{
checkStatistics( resultSummary.counters(), tableToValueMap( expectedStatistics ) );
}
}
@Then( "^requesting the `Statement Type` should give (.*)$" )
public void theStatementTypeShouldBeType( String expectedType ) throws Throwable
{
StatementType expected;
switch ( expectedType )
{
case "read only":
expected = StatementType.READ_ONLY;
break;
case "write only":
expected = StatementType.WRITE_ONLY;
break;
case "read write":
expected = StatementType.READ_WRITE;
break;
case "schema write":
expected = StatementType.SCHEMA_WRITE;
break;
default:
throw new IllegalArgumentException( "Nu such type: " + expectedType );
}
for ( ResultSummary summary : summaries )
{
assertThat( summary.statementType(), equalTo( expected ) );
}
}
@Then( "^the `Result Summary` has a `Plan`$" )
public void theSummaryHasAPlan() throws Throwable
{
for ( ResultSummary summary : summaries )
{
assertThat( summary.hasPlan(), equalTo( true ) );
}
}
@Then( "^the `Result Summary` does not have a `Plan`$" )
public void theSummaryDoesNotHaveAPlan() throws Throwable
{
for ( ResultSummary summary : summaries )
{
assertThat( summary.hasPlan(), equalTo( false ) );
}
}
@Then( "^the `Result Summary` has a `Profile`$" )
public void theSummaryHasAProfile() throws Throwable
{
for ( ResultSummary summary : summaries )
{
assertThat( summary.hasProfile(), equalTo( true ) );
}
}
@And( "^the `Result Summary` does not have a `Profile`$" )
public void theSummaryDoesNotHaveAPriofile() throws Throwable
{
for ( ResultSummary summary : summaries )
{
assertThat( summary.hasProfile(), equalTo( false ) );
}
}
@Then( "^requesting the `Plan` it contains$" )
public void thePlanContains( DataTable expectedTable ) throws Throwable
{
HashMap<String,Object> expectedMap = tableToValueMap( expectedTable );
for ( ResultSummary summary : summaries )
{
checkPlansValues( expectedMap, summary.plan() );
}
}
@Then( "^requesting the `Profile` it contains:$" )
public void theProfileContains( DataTable expectedTable ) throws Throwable
{
HashMap<String,Object> expectedMap = tableToValueMap( expectedTable );
for ( ResultSummary summary : summaries )
{
checkPlansValues( expectedMap, summary.profile() );
}
}
@And( "^the `Plan` also contains method calls for:$" )
public void alsoContainsMethodCallsFor( DataTable expectedTable ) throws Throwable
{
HashMap<String,String> expectedMap = tableToMap( expectedTable );
for ( ResultSummary summary : summaries )
{
checkPlanMethods( expectedMap, summary.plan() );
}
}
@And( "^the `Profile` also contains method calls for:$" )
public void profileAlsoContainsMethodCallsFor( DataTable expectedTable ) throws Throwable
{
HashMap<String,String> expectedMap = tableToMap( expectedTable );
for ( ResultSummary summary : summaries )
{
checkPlanMethods( expectedMap, summary.profile() );
}
}
@And( "^the `Result Summary` `Notifications` is empty$" )
public void theSummaryDoesNotHaveAnyNotifications() throws Throwable
{
for ( ResultSummary summary : summaries )
{
assertThat( summary.notifications().size(), equalTo( 0 ) );
}
}
@And( "^the `Result Summary` `Notifications` has one notification with$" )
public void theSummaryHasNotifications( DataTable table ) throws Throwable
{
HashMap<String,Object> expected = new HashMap<>();
for ( int i = 1; i < table.diffableRows().size(); i++ )
{
DiffableRow row = table.diffableRows().get( i );
String key = row.convertedRow.get( 0 );
Object value = getJavaValueNormalInts( row.convertedRow.get( 1 ) );
expected.put( key, value );
}
for ( ResultSummary summary : summaries )
{
assertThat( summary.notifications().size() == 1, equalTo( true ) );
Notification notification = summary.notifications().get( 0 );
for ( String key : expected.keySet() )
{
switch ( key )
{
case "code":
compareNotificationValue( notification.code(), expected.get( key ) );
break;
case "title":
compareNotificationValue( notification.title(), expected.get( key ) );
break;
case "description":
compareNotificationValue( notification.description(), expected.get( key ) );
break;
case "severity":
compareNotificationValue( notification.severity(), expected.get( key ) );
break;
case "position":
Map<String,Object> expectedPosition = (Map<String,Object>) expected.get( key );
InputPosition position = notification.position();
for ( String positionKey : expectedPosition.keySet() )
{
switch ( positionKey )
{
case "offset":
compareNotificationValue( position.offset(), expectedPosition.get( positionKey ) );
break;
case "line":
compareNotificationValue( position.line(), expectedPosition.get( positionKey ) );
break;
case "column":
compareNotificationValue( position.column(), expectedPosition.get( positionKey ) );
break;
}
}
break;
default:
throw new IllegalArgumentException( "No case for " + key );
}
}
}
}
private void compareNotificationValue(Object given, Object expected )
{
assertThat( given, IsInstanceOf.instanceOf( expected.getClass() ) );
assertThat( given, CoreMatchers.<Object>equalTo( expected ) );
}
public HashMap<String,Object> tableToValueMap( DataTable table )
{
HashMap<String,Object> map = new HashMap<>();
for ( int i = 1; i < table.diffableRows().size(); i++ )
{
List<String> row = table.diffableRows().get( i ).convertedRow;
map.put( row.get( 0 ), getJavaValueIntAsLong( row.get( 1 ) ) );
}
return map;
}
public HashMap<String,String> tableToMap( DataTable table )
{
HashMap<String,String> map = new HashMap<>();
for ( int i = 1; i < table.diffableRows().size(); i++ )
{
List<String> row = table.diffableRows().get( i ).convertedRow;
map.put( row.get( 0 ), row.get( 1 ) );
}
return map;
}
private void checkPlansValues( HashMap<String,Object> expected, Plan p )
{
for ( String key : expected.keySet() )
{
Object givenValue;
switch ( key )
{
case "identifiers":
givenValue = p.identifiers();
break;
case "operator type":
givenValue = p.operatorType();
break;
case "db hits":
assertThat( p, instanceOf( ProfiledPlan.class ) );
givenValue = ((ProfiledPlan) p).dbHits();
break;
case "records":
assertThat( p, instanceOf( ProfiledPlan.class ) );
givenValue = ((ProfiledPlan) p).records();
break;
default:
throw new IllegalArgumentException( "Nu such plan method: " + key );
}
assertThat( key + "- does not match", givenValue, equalTo( expected.get( key ) ) );
}
}
private void checkPlanMethods( HashMap<String,String> expected, Plan plan )
{
for ( String key : expected.keySet() )
{
switch ( key )
{
case "children":
assertThat( plan.children(), instanceOf( List.class ) );
for ( Plan p : plan.children() )
{
assertThat( p, instanceOf( Plan.class ) );
break;
}
break;
case "arguments":
assertThat( plan.arguments(), instanceOf( Map.class ) );
for ( String k : plan.arguments().keySet() )
{
assertThat( k, instanceOf( String.class ) );
assertThat( plan.arguments().get( k ), instanceOf( Value.class ) );
break;
}
break;
default:
throw new IllegalArgumentException( "There is no case for handeling method type: " + key );
}
}
}
private void checkStatistics( SummaryCounters statistics, Map<String,Object> expectedStatisticsMap )
{
for ( String key : expectedStatisticsMap.keySet() )
{
Object expectedValue = expectedStatisticsMap.get( key );
Object givenValue;
switch ( key )
{
case "nodes created":
givenValue = statistics.nodesCreated();
break;
case "nodes deleted":
givenValue = statistics.nodesDeleted();
break;
case "relationships created":
givenValue = statistics.relationshipsCreated();
break;
case "relationships deleted":
givenValue = statistics.relationshipsDeleted();
break;
case "properties set":
givenValue = statistics.propertiesSet();
break;
case "labels added":
givenValue = statistics.labelsAdded();
break;
case "labels removed":
givenValue = statistics.labelsRemoved();
break;
case "indexes added":
givenValue = statistics.indexesAdded();
break;
case "indexes removed":
givenValue = statistics.indexesRemoved();
break;
case "constraints added":
givenValue = statistics.constraintsAdded();
break;
case "constraints removed":
givenValue = statistics.constraintsRemoved();
break;
case "contains updates":
givenValue = statistics.containsUpdates();
break;
default:
throw new IllegalArgumentException( "No function mapped to expression: " + key );
}
if ( givenValue instanceof Integer )
{
givenValue = Long.valueOf( (Integer) givenValue );
}
assertThat( key + " - did not match", givenValue, equalTo( expectedValue ) );
}
}
}