/*
 * Copyright 2011-2018 the original author or authors.
 *
 * 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.springframework.data.mongodb.repository.query;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
import static org.springframework.data.mongodb.repository.query.StubParameterAccessor.*;

import java.lang.reflect.Method;
import java.util.List;

import org.bson.types.ObjectId;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.data.domain.Range;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Polygon;
import org.springframework.data.geo.Shape;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.Person;
import org.springframework.data.mongodb.core.Venue;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.geo.GeoJsonLineString;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.query.parser.PartTree;

import org.bson.Document;

/**
 * Unit test for {@link MongoQueryCreator}.
 *
 * @author Oliver Gierke
 * @author Thomas Darimont
 * @author Christoph Strobl
 */
public class MongoQueryCreatorUnitTests {

	MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context;
	MongoConverter converter;

	@Rule public ExpectedException expection = ExpectedException.none();

	@Before
	public void setUp() throws SecurityException, NoSuchMethodException {

		context = new MongoMappingContext();

		DbRefResolver resolver = new DefaultDbRefResolver(mock(MongoDbFactory.class));
		converter = new MappingMongoConverter(resolver, context);
	}

	@Test
	public void createsQueryCorrectly() throws Exception {

		PartTree tree = new PartTree("findByFirstName", Person.class);

		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "Oliver"), context);
		Query query = creator.createQuery();
		assertThat(query, is(query(where("firstName").is("Oliver"))));
	}

	@Test // DATAMONGO-469
	public void createsAndQueryCorrectly() {

		Person person = new Person();
		MongoQueryCreator creator = new MongoQueryCreator(new PartTree("findByFirstNameAndFriend", Person.class),
				getAccessor(converter, "Oliver", person), context);
		Query query = creator.createQuery();

		assertThat(query, is(query(where("firstName").is("Oliver").and("friend").is(person))));
	}

	@Test
	public void createsNotNullQueryCorrectly() {

		PartTree tree = new PartTree("findByFirstNameNotNull", Person.class);
		Query query = new MongoQueryCreator(tree, getAccessor(converter), context).createQuery();

		assertThat(query, is(new Query(Criteria.where("firstName").ne(null))));
	}

	@Test
	public void createsIsNullQueryCorrectly() {

		PartTree tree = new PartTree("findByFirstNameIsNull", Person.class);
		Query query = new MongoQueryCreator(tree, getAccessor(converter), context).createQuery();

		assertThat(query, is(new Query(Criteria.where("firstName").is(null))));
	}

	@Test
	public void bindsMetricDistanceParameterToNearSphereCorrectly() throws Exception {

		Point point = new Point(10, 20);
		Distance distance = new Distance(2.5, Metrics.KILOMETERS);

		Query query = query(
				where("location").nearSphere(point).maxDistance(distance.getNormalizedValue()).and("firstname").is("Dave"));
		assertBindsDistanceToQuery(point, distance, query);
	}

	@Test
	public void bindsDistanceParameterToNearCorrectly() throws Exception {

		Point point = new Point(10, 20);
		Distance distance = new Distance(2.5);

		Query query = query(
				where("location").near(point).maxDistance(distance.getNormalizedValue()).and("firstname").is("Dave"));
		assertBindsDistanceToQuery(point, distance, query);
	}

	@Test
	public void createsLessThanEqualQueryCorrectly() throws Exception {

		PartTree tree = new PartTree("findByAgeLessThanEqual", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, 18), context);

		Query reference = query(where("age").lte(18));
		assertThat(creator.createQuery(), is(reference));
	}

	@Test
	public void createsGreaterThanEqualQueryCorrectly() throws Exception {

		PartTree tree = new PartTree("findByAgeGreaterThanEqual", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, 18), context);

		Query reference = query(where("age").gte(18));
		assertThat(creator.createQuery(), is(reference));
	}

	@Test // DATAMONGO-338
	public void createsExistsClauseCorrectly() {

		PartTree tree = new PartTree("findByAgeExists", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, true), context);
		Query query = query(where("age").exists(true));
		assertThat(creator.createQuery(), is(query));
	}

	@Test // DATAMONGO-338
	public void createsRegexClauseCorrectly() {

		PartTree tree = new PartTree("findByFirstNameRegex", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, ".*"), context);
		Query query = query(where("firstName").regex(".*"));
		assertThat(creator.createQuery(), is(query));
	}

	@Test // DATAMONGO-338
	public void createsTrueClauseCorrectly() {

		PartTree tree = new PartTree("findByActiveTrue", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter), context);
		Query query = query(where("active").is(true));
		assertThat(creator.createQuery(), is(query));
	}

	@Test // DATAMONGO-338
	public void createsFalseClauseCorrectly() {

		PartTree tree = new PartTree("findByActiveFalse", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter), context);
		Query query = query(where("active").is(false));
		assertThat(creator.createQuery(), is(query));
	}

	@Test // DATAMONGO-413
	public void createsOrQueryCorrectly() {

		PartTree tree = new PartTree("findByFirstNameOrAge", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "Dave", 42), context);

		Query query = creator.createQuery();
		assertThat(query, is(query(new Criteria().orOperator(where("firstName").is("Dave"), where("age").is(42)))));
	}

	@Test // DATAMONGO-347
	public void createsQueryReferencingADBRefCorrectly() {

		User user = new User();
		user.id = new ObjectId();

		PartTree tree = new PartTree("findByCreator", User.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, user), context);
		Document queryObject = creator.createQuery().getQueryObject();

		assertThat(queryObject.get("creator"), is((Object) user));
	}

	@Test // DATAMONGO-418
	public void createsQueryWithStartingWithPredicateCorrectly() {

		PartTree tree = new PartTree("findByUsernameStartingWith", User.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "Matt"), context);
		Query query = creator.createQuery();

		assertThat(query, is(query(where("username").regex("^Matt"))));
	}

	@Test // DATAMONGO-418
	public void createsQueryWithEndingWithPredicateCorrectly() {

		PartTree tree = new PartTree("findByUsernameEndingWith", User.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "ews"), context);
		Query query = creator.createQuery();

		assertThat(query, is(query(where("username").regex("ews$"))));
	}

	@Test // DATAMONGO-418
	public void createsQueryWithContainingPredicateCorrectly() {

		PartTree tree = new PartTree("findByUsernameContaining", User.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "thew"), context);
		Query query = creator.createQuery();

		assertThat(query, is(query(where("username").regex(".*thew.*"))));
	}

	private void assertBindsDistanceToQuery(Point point, Distance distance, Query reference) throws Exception {

		PartTree tree = new PartTree("findByLocationNearAndFirstname",
				org.springframework.data.mongodb.repository.Person.class);
		Method method = PersonRepository.class.getMethod("findByLocationNearAndFirstname", Point.class, Distance.class,
				String.class);
		MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class),
				new SpelAwareProxyProjectionFactory(), new MongoMappingContext());
		MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod,
				new Object[] { point, distance, "Dave" });

		Query query = new MongoQueryCreator(tree, new ConvertingParameterAccessor(converter, accessor), context)
				.createQuery();
		assertThat(query, is(query));
	}

	@Test // DATAMONGO-770
	public void createsQueryWithFindByIgnoreCaseCorrectly() {

		PartTree tree = new PartTree("findByfirstNameIgnoreCase", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context);

		Query query = creator.createQuery();
		assertThat(query, is(query(where("firstName").regex("^dave$", "i"))));
	}

	@Test // DATAMONGO-770
	public void createsQueryWithFindByNotIgnoreCaseCorrectly() {

		PartTree tree = new PartTree("findByFirstNameNotIgnoreCase", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context);

		Query query = creator.createQuery();
		assertThat(query.toString(), is(query(where("firstName").not().regex("^dave$", "i")).toString()));
	}

	@Test // DATAMONGO-770
	public void createsQueryWithFindByStartingWithIgnoreCaseCorrectly() {

		PartTree tree = new PartTree("findByFirstNameStartingWithIgnoreCase", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context);

		Query query = creator.createQuery();
		assertThat(query, is(query(where("firstName").regex("^dave", "i"))));
	}

	@Test // DATAMONGO-770
	public void createsQueryWithFindByEndingWithIgnoreCaseCorrectly() {

		PartTree tree = new PartTree("findByFirstNameEndingWithIgnoreCase", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context);

		Query query = creator.createQuery();
		assertThat(query, is(query(where("firstName").regex("dave$", "i"))));
	}

	@Test // DATAMONGO-770
	public void createsQueryWithFindByContainingIgnoreCaseCorrectly() {

		PartTree tree = new PartTree("findByFirstNameContainingIgnoreCase", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context);

		Query query = creator.createQuery();
		assertThat(query, is(query(where("firstName").regex(".*dave.*", "i"))));
	}

	@Test // DATAMONGO-770
	public void shouldThrowExceptionForQueryWithFindByIgnoreCaseOnNonStringProperty() {

		expection.expect(IllegalArgumentException.class);
		expection.expectMessage("must be of type String");

		PartTree tree = new PartTree("findByFirstNameAndAgeIgnoreCase", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "foo", 42), context);

		creator.createQuery();
	}

	@Test // DATAMONGO-770
	public void shouldOnlyGenerateLikeExpressionsForStringPropertiesIfAllIgnoreCase() {

		PartTree tree = new PartTree("findByFirstNameAndAgeAllIgnoreCase", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave", 42), context);

		Query query = creator.createQuery();
		assertThat(query, is(query(where("firstName").regex("^dave$", "i").and("age").is(42))));
	}

	@Test // DATAMONGO-566
	public void shouldCreateDeleteByQueryCorrectly() {

		PartTree tree = new PartTree("deleteByFirstName", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave", 42), context);

		Query query = creator.createQuery();

		assertThat(tree.isDelete(), is(true));
		assertThat(query, is(query(where("firstName").is("dave"))));
	}

	@Test // DATAMONGO-566
	public void shouldCreateDeleteByQueryCorrectlyForMultipleCriteriaAndCaseExpressions() {

		PartTree tree = new PartTree("deleteByFirstNameAndAgeAllIgnoreCase", Person.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave", 42), context);

		Query query = creator.createQuery();

		assertThat(tree.isDelete(), is(true));
		assertThat(query, is(query(where("firstName").regex("^dave$", "i").and("age").is(42))));
	}

	@Test // DATAMONGO-1075
	public void shouldCreateInClauseWhenUsingContainsOnCollectionLikeProperty() {

		PartTree tree = new PartTree("findByEmailAddressesContaining", User.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context);

		Query query = creator.createQuery();

		assertThat(query, is(query(where("emailAddresses").in("dave"))));
	}

	@Test // DATAMONGO-1075
	public void shouldCreateInClauseWhenUsingNotContainsOnCollectionLikeProperty() {

		PartTree tree = new PartTree("findByEmailAddressesNotContaining", User.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context);

		Query query = creator.createQuery();

		assertThat(query, is(query(where("emailAddresses").not().in("dave"))));
	}

	@Test // DATAMONGO-1075, DATAMONGO-1425
	public void shouldCreateRegexWhenUsingNotContainsOnStringProperty() {

		PartTree tree = new PartTree("findByUsernameNotContaining", User.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "thew"), context);
		Query query = creator.createQuery();

		assertThat(query.getQueryObject().toJson(), is(query(where("username").not().regex(".*thew.*")).getQueryObject().toJson()));
	}

	@Test // DATAMONGO-1139
	public void createsNonSphericalNearForDistanceWithDefaultMetric() {

		Point point = new Point(1.0, 1.0);
		Distance distance = new Distance(1.0);

		PartTree tree = new PartTree("findByLocationNear", Venue.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, point, distance), context);
		Query query = creator.createQuery();

		assertThat(query, is(query(where("location").near(point).maxDistance(1.0))));
	}

	@Test // DATAMONGO-1136
	public void shouldCreateWithinQueryCorrectly() {

		Point first = new Point(1, 1);
		Point second = new Point(2, 2);
		Point third = new Point(3, 3);
		Shape shape = new Polygon(first, second, third);

		PartTree tree = new PartTree("findByAddress_GeoWithin", User.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, shape), context);
		Query query = creator.createQuery();

		assertThat(query, is(query(where("address.geo").within(shape))));
	}

	@Test // DATAMONGO-1110
	public void shouldCreateNearSphereQueryForSphericalProperty() {

		Point point = new Point(10, 20);

		PartTree tree = new PartTree("findByAddress2dSphere_GeoNear", User.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, point), context);
		Query query = creator.createQuery();

		assertThat(query, is(query(where("address2dSphere.geo").nearSphere(point))));
	}

	@Test // DATAMONGO-1110
	public void shouldCreateNearSphereQueryForSphericalPropertyHavingDistanceWithDefaultMetric() {

		Point point = new Point(1.0, 1.0);
		Distance distance = new Distance(1.0);

		PartTree tree = new PartTree("findByAddress2dSphere_GeoNear", User.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, point, distance), context);
		Query query = creator.createQuery();

		assertThat(query, is(query(where("address2dSphere.geo").nearSphere(point).maxDistance(1.0))));
	}

	@Test // DATAMONGO-1110
	public void shouldCreateNearQueryForMinMaxDistance() {

		Point point = new Point(10, 20);
		Range<Distance> range = Distance.between(new Distance(10), new Distance(20));

		PartTree tree = new PartTree("findByAddress_GeoNear", User.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, point, range), context);
		Query query = creator.createQuery();

		assertThat(query, is(query(where("address.geo").near(point).minDistance(10D).maxDistance(20D))));
	}

	@Test // DATAMONGO-1229
	public void appliesIgnoreCaseToLeafProperty() {

		PartTree tree = new PartTree("findByAddressStreetIgnoreCase", User.class);
		ConvertingParameterAccessor accessor = getAccessor(converter, "Street");

		assertThat(new MongoQueryCreator(tree, accessor, context).createQuery(), is(notNullValue()));
	}

	@Test // DATAMONGO-1232
	public void ignoreCaseShouldEscapeSource() {

		PartTree tree = new PartTree("findByUsernameIgnoreCase", User.class);
		ConvertingParameterAccessor accessor = getAccessor(converter, "con.flux+");

		Query query = new MongoQueryCreator(tree, accessor, context).createQuery();

		assertThat(query, is(query(where("username").regex("^\\Qcon.flux+\\E$", "i"))));
	}

	@Test // DATAMONGO-1232
	public void ignoreCaseShouldEscapeSourceWhenUsedForStartingWith() {

		PartTree tree = new PartTree("findByUsernameStartingWithIgnoreCase", User.class);
		ConvertingParameterAccessor accessor = getAccessor(converter, "dawns.light+");

		Query query = new MongoQueryCreator(tree, accessor, context).createQuery();

		assertThat(query, is(query(where("username").regex("^\\Qdawns.light+\\E", "i"))));
	}

	@Test // DATAMONGO-1232
	public void ignoreCaseShouldEscapeSourceWhenUsedForEndingWith() {

		PartTree tree = new PartTree("findByUsernameEndingWithIgnoreCase", User.class);
		ConvertingParameterAccessor accessor = getAccessor(converter, "new.ton+");

		Query query = new MongoQueryCreator(tree, accessor, context).createQuery();

		assertThat(query, is(query(where("username").regex("\\Qnew.ton+\\E$", "i"))));
	}

	@Test // DATAMONGO-1232
	public void likeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() {

		PartTree tree = new PartTree("findByUsernameLike", User.class);
		ConvertingParameterAccessor accessor = getAccessor(converter, "*fire.fight+*");

		Query query = new MongoQueryCreator(tree, accessor, context).createQuery();

		assertThat(query, is(query(where("username").regex(".*\\Qfire.fight+\\E.*"))));
	}

	@Test // DATAMONGO-1232
	public void likeShouldEscapeSourceWhenUsedWithLeadingWildcard() {

		PartTree tree = new PartTree("findByUsernameLike", User.class);
		ConvertingParameterAccessor accessor = getAccessor(converter, "*steel.heart+");

		Query query = new MongoQueryCreator(tree, accessor, context).createQuery();

		assertThat(query, is(query(where("username").regex(".*\\Qsteel.heart+\\E"))));
	}

	@Test // DATAMONGO-1232
	public void likeShouldEscapeSourceWhenUsedWithTrailingWildcard() {

		PartTree tree = new PartTree("findByUsernameLike", User.class);
		ConvertingParameterAccessor accessor = getAccessor(converter, "cala.mity+*");

		Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
		assertThat(query, is(query(where("username").regex("\\Qcala.mity+\\E.*"))));
	}

	@Test // DATAMONGO-1232
	public void likeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() {

		PartTree tree = new PartTree("findByUsernameLike", User.class);
		ConvertingParameterAccessor accessor = getAccessor(converter, "*");

		Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
		assertThat(query, is(query(where("username").regex(".*"))));
	}

	@Test // DATAMONGO-1342
	public void bindsNullValueToContainsClause() {

		PartTree partTree = new PartTree("emailAddressesContains", User.class);

		ConvertingParameterAccessor accessor = getAccessor(converter, new Object[] { null });
		Query query = new MongoQueryCreator(partTree, accessor, context).createQuery();

		assertThat(query, is(query(where("emailAddresses").in((Object) null))));
	}

	@Test // DATAMONGO-1424
	public void notLikeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() {

		PartTree tree = new PartTree("findByUsernameNotLike", User.class);
		ConvertingParameterAccessor accessor = getAccessor(converter, "*fire.fight+*");

		Query query = new MongoQueryCreator(tree, accessor, context).createQuery();

		assertThat(query.getQueryObject().toJson(),
				is(query(where("username").not().regex(".*\\Qfire.fight+\\E.*")).getQueryObject().toJson()));
	}

	@Test // DATAMONGO-1424
	public void notLikeShouldEscapeSourceWhenUsedWithLeadingWildcard() {

		PartTree tree = new PartTree("findByUsernameNotLike", User.class);
		ConvertingParameterAccessor accessor = getAccessor(converter, "*steel.heart+");

		Query query = new MongoQueryCreator(tree, accessor, context).createQuery();

		assertThat(query.getQueryObject().toJson(),
				is(query(where("username").not().regex(".*\\Qsteel.heart+\\E")).getQueryObject().toJson()));
	}

	@Test // DATAMONGO-1424
	public void notLikeShouldEscapeSourceWhenUsedWithTrailingWildcard() {

		PartTree tree = new PartTree("findByUsernameNotLike", User.class);
		MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "cala.mity+*"), context);
		Query query = creator.createQuery();

		assertThat(query.getQueryObject().toJson(), is(query(where("username").not().regex("\\Qcala.mity+\\E.*")).getQueryObject().toJson()));
	}

	@Test // DATAMONGO-1424
	public void notLikeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() {

		PartTree tree = new PartTree("findByUsernameNotLike", User.class);
		ConvertingParameterAccessor accessor = getAccessor(converter, "*");

		Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
		assertThat(query.getQueryObject().toJson(), is(query(where("username").not().regex(".*")).getQueryObject().toJson()));
	}

	@Test // DATAMONGO-1588
	public void queryShouldAcceptSubclassOfDeclaredArgument() {

		PartTree tree = new PartTree("findByLocationNear", User.class);
		ConvertingParameterAccessor accessor = getAccessor(converter, new GeoJsonPoint(-74.044502D, 40.689247D));

		Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
		assertThat(query.getQueryObject().containsKey("location"), is(true));
	}

	@Test // DATAMONGO-1588
	public void queryShouldThrowExceptionWhenArgumentDoesNotMatchDeclaration() {

		expection.expect(IllegalArgumentException.class);
		expection.expectMessage("Expected parameter type of " + Point.class);

		PartTree tree = new PartTree("findByLocationNear", User.class);
		ConvertingParameterAccessor accessor = getAccessor(converter,
				new GeoJsonLineString(new Point(-74.044502D, 40.689247D), new Point(-73.997330D, 40.730824D)));

		new MongoQueryCreator(tree, accessor, context).createQuery();
	}

	interface PersonRepository extends Repository<Person, Long> {

		List<Person> findByLocationNearAndFirstname(Point location, Distance maxDistance, String firstname);
	}

	class User {

		ObjectId id;

		@Field("foo") String username;

		@DBRef User creator;

		List<String> emailAddresses;

		Address address;

		Address2dSphere address2dSphere;

		Point location;
	}

	static class Address {

		String street;

		Point geo;
	}

	static class Address2dSphere {

		String street;

		@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) Point geo;
	}
}
