/* * 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.internal; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.value.NullValue; import org.neo4j.driver.v1.Record; import org.neo4j.driver.v1.Statement; import org.neo4j.driver.v1.StatementResult; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.exceptions.NoSuchRecordException; import org.neo4j.driver.v1.util.Pair; import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.neo4j.driver.v1.Records.column; import static org.neo4j.driver.v1.Values.ofString; import static org.neo4j.driver.v1.Values.value; public class InternalStatementResultTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @Test public void iterationShouldWorksAsExpected() { // GIVEN StatementResult result = createResult( 3 ); // WHEN assertTrue( result.hasNext() ); assertThat( values( result.next() ), equalTo( asList( value( "v1-1" ), value( "v2-1" ) ) ) ); assertTrue( result.hasNext() ); assertThat( values( result.next() ), equalTo( asList( value( "v1-2" ), value( "v2-2" ) ) ) ); assertTrue( result.hasNext() ); //1 -> 2 // THEN assertThat( values( result.next() ), equalTo( asList( value( "v1-3" ), value( "v2-3" ) ) ) ); assertFalse( result.hasNext() ); expectedException.expect( NoSuchRecordException.class ); // WHEN assertNull( result.next() ); } @Test public void firstOfFieldNameShouldWorkAsExpected() { // GIVEN StatementResult result = createResult( 3 ); // THEN assertThat( result.next().get( "k1" ), equalTo( value( "v1-1" ) ) ); assertTrue( result.hasNext() ); } @Test public void firstOfFieldIndexShouldWorkAsExpected() { // GIVEN StatementResult result = createResult( 3 ); // THEN assertThat( result.next().get( 0 ), equalTo( value( "v1-1" ) ) ); assertTrue( result.hasNext() ); } @Test public void singlePastFirstShouldFail() { // GIVEN StatementResult result = createResult( 2 ); result.next(); result.next(); // THEN expectedException.expect( NoSuchRecordException.class ); // THEN result.single(); } @Test public void singleNoneShouldFail() { // GIVEN StatementResult result = createResult( 0 ); // THEN expectedException.expect( NoSuchRecordException.class ); // THEN result.single(); } @Test public void singleWhenMoreThanOneShouldFail() { // GIVEN StatementResult result = createResult( 2 ); // THEN expectedException.expect( NoSuchRecordException.class ); // THEN result.single(); } @Test public void singleOfFieldNameShouldWorkAsExpected() { // GIVEN StatementResult result = createResult( 1 ); // THEN assertThat( result.single().get( "k1" ), equalTo( value( "v1-1" ) ) ); assertFalse( result.hasNext() ); } @Test public void singleOfFieldIndexShouldWorkAsExpected() { // GIVEN StatementResult result = createResult( 1 ); // THEN assertThat( result.single().get( 0 ), equalTo( value( "v1-1" ) ) ); assertFalse( result.hasNext() ); } @Test public void singleShouldWorkAsExpected() { assertNotNull( createResult( 1 ).single() ); } @Test public void singleShouldThrowOnBigResult() { // Expect expectedException.expect( NoSuchRecordException.class ); // When createResult( 42 ).single(); } @Test public void singleShouldThrowOnEmptyResult() { // Expect expectedException.expect( NoSuchRecordException.class ); // When createResult( 0 ).single(); } @Test public void singleShouldThrowOnConsumedResult() { // Expect expectedException.expect( NoSuchRecordException.class ); // When StatementResult result = createResult( 2 ); result.consume(); result.single(); } @Test public void shouldConsumeTwice() { // GIVEN StatementResult result = createResult( 2 ); result.consume(); // WHEN result.consume(); // THEN assertFalse( result.hasNext() ); } @Test public void shouldList() { // GIVEN StatementResult result = createResult( 2 ); List<String> records = result.list( column( "k1", ofString() ) ); // THEN assertThat( records, equalTo( asList( "v1-1", "v1-2" ) ) ); } @Test public void shouldListTwice() { // GIVEN StatementResult result = createResult( 2 ); List<Record> firstList = result.list(); assertThat( firstList.size(), equalTo( 2 ) ); // THEN List<Record> secondList = result.list(); assertThat( secondList.size(), equalTo( 0 ) ); } @Test public void singleShouldNotThrowOnPartiallyConsumedResult() { // Given StatementResult result = createResult( 2 ); result.next(); // When + Then assertNotNull( result.single() ); } @Test public void singleShouldConsumeIfFailing() { // Given StatementResult result = createResult( 2 ); try { result.single(); fail( "Exception expected" ); } catch ( NoSuchRecordException e ) { assertFalse( result.hasNext() ); } } @Test public void retainShouldWorkAsExpected() { // GIVEN StatementResult result = createResult( 3 ); // WHEN List<Record> records = result.list(); // THEN assertFalse( result.hasNext() ); assertThat( records, hasSize( 3 ) ); } @Test public void retainAndMapByKeyShouldWorkAsExpected() { // GIVEN StatementResult result = createResult( 3 ); // WHEN List<Value> records = result.list( column( "k1" ) ); // THEN assertFalse( result.hasNext() ); assertThat( records, hasSize( 3 ) ); } @Test public void retainAndMapByIndexShouldWorkAsExpected() { // GIVEN StatementResult result = createResult( 3 ); // WHEN List<Value> records = result.list( column( 0 ) ); // THEN assertFalse( result.hasNext() ); assertThat( records, hasSize( 3 ) ); } @Test public void accessingOutOfBoundsShouldBeNull() { // GIVEN StatementResult result = createResult( 1 ); // WHEN Record record = result.single(); // THEN assertThat( record.get( 0 ), equalTo( value( "v1-1" ) ) ); assertThat( record.get( 1 ), equalTo( value( "v2-1" ) ) ); assertThat( record.get( 2 ), equalTo( NullValue.NULL ) ); assertThat( record.get( -37 ), equalTo( NullValue.NULL ) ); } @Test public void accessingKeysWithoutCallingNextShouldNotFail() { // GIVEN StatementResult result = createResult( 11 ); // WHEN // not calling next or single // THEN assertThat( result.keys(), equalTo( asList( "k1", "k2" ) ) ); } @Test public void shouldPeekIntoTheFuture() { // WHEN StatementResult result = createResult( 2 ); // THEN assertThat( result.peek().get( "k1" ), equalTo( value( "v1-1" ) ) ); // WHEN result.next(); // THEN assertThat( result.peek().get( "k1" ), equalTo( value( "v1-2" ) ) ); // WHEN result.next(); // THEN expectedException.expect( NoSuchRecordException.class ); // WHEN result.peek(); } @Test public void shouldNotPeekIntoTheFutureWhenResultIsEmpty() { // GIVEN StatementResult result = createResult( 0 ); // THEN expectedException.expect( NoSuchRecordException.class ); // WHEN Record future = result.peek(); } @Test public void shouldNotifyResourcesHandlerWhenFetchedViaList() { SessionResourcesHandler resourcesHandler = mock( SessionResourcesHandler.class ); StatementResult result = createResult( 10, resourcesHandler ); List<Record> records = result.list(); assertEquals( 10, records.size() ); verify( resourcesHandler ).onResultConsumed(); } @Test public void shouldNotifyResourcesHandlerWhenFetchedViaSingle() { SessionResourcesHandler resourcesHandler = mock( SessionResourcesHandler.class ); StatementResult result = createResult( 1, resourcesHandler ); Record record = result.single(); assertEquals( "v1-1", record.get( "k1" ).asString() ); verify( resourcesHandler ).onResultConsumed(); } @Test public void shouldNotifyResourcesHandlerWhenFetchedViaIterator() { SessionResourcesHandler resourcesHandler = mock( SessionResourcesHandler.class ); StatementResult result = createResult( 1, resourcesHandler ); while ( result.hasNext() ) { assertNotNull( result.next() ); } verify( resourcesHandler ).onResultConsumed(); } @Test public void shouldNotifyResourcesHandlerWhenSummary() { SessionResourcesHandler resourcesHandler = mock( SessionResourcesHandler.class ); StatementResult result = createResult( 10, resourcesHandler ); assertNotNull( result.summary() ); verify( resourcesHandler ).onResultConsumed(); } @Test public void shouldNotifyResourcesHandlerWhenConsumed() { SessionResourcesHandler resourcesHandler = mock( SessionResourcesHandler.class ); StatementResult result = createResult( 5, resourcesHandler ); result.consume(); verify( resourcesHandler ).onResultConsumed(); } @Test public void shouldNotifyResourcesHandlerOnlyOnceWhenConsumed() { SessionResourcesHandler resourcesHandler = mock( SessionResourcesHandler.class ); StatementResult result = createResult( 8, resourcesHandler ); assertEquals( 8, result.list().size() ); assertNotNull( result.summary() ); assertNotNull( result.consume() ); assertNotNull( result.summary() ); verify( resourcesHandler ).onResultConsumed(); } private StatementResult createResult( int numberOfRecords ) { return createResult( numberOfRecords, SessionResourcesHandler.NO_OP ); } private StatementResult createResult( int numberOfRecords, SessionResourcesHandler resourcesHandler ) { Connection connection = mock( Connection.class ); String statement = "<unknown>"; final InternalStatementResult result = new InternalStatementResult( connection, resourcesHandler, null, new Statement( statement ) ); // Each time the cursor calls `recieveOne`, we'll run one of these, // to emulate how messages are handed over to the cursor final LinkedList<Runnable> inboundMessages = new LinkedList<>(); inboundMessages.add( streamHeadMessage( result ) ); for ( int i = 1; i <= numberOfRecords; i++ ) { inboundMessages.add( recordMessage( result, i ) ); } inboundMessages.add( streamTailMessage( result ) ); doAnswer( new Answer() { @Override public Object answer( InvocationOnMock invocationOnMock ) throws Throwable { inboundMessages.poll().run(); return null; } } ).when( connection ).receiveOne(); return result; } private Runnable streamTailMessage( final InternalStatementResult cursor ) { return new Runnable() { @Override public void run() { cursor.pullAllResponseCollector().done(); } }; } private Runnable recordMessage( final InternalStatementResult cursor, final int val ) { return new Runnable() { @Override public void run() { cursor.pullAllResponseCollector().record( new Value[]{value( "v1-" + val ), value( "v2-" + val )} ); } }; } private Runnable streamHeadMessage( final InternalStatementResult cursor ) { return new Runnable() { @Override public void run() { cursor.runResponseCollector().keys( new String[]{"k1", "k2"} ); cursor.runResponseCollector().done(); } }; } private List<Value> values( Record record ) { List<Value> result = new ArrayList<>( record.keys().size() ); for ( Pair<String,Value> property : record.fields() ) { result.add( property.value() ); } return result; } }