package org.mongodb.morphia.query;
import com.mongodb.BasicDBObject;
import org.bson.types.ObjectId;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mongodb.morphia.Key;
import org.mongodb.morphia.annotations.Reference;
import org.mongodb.morphia.annotations.Serialized;
import org.mongodb.morphia.entities.EntityWithListsAndArrays;
import org.mongodb.morphia.entities.SimpleEntity;
import org.mongodb.morphia.mapping.MappedClass;
import org.mongodb.morphia.mapping.MappedField;
import org.mongodb.morphia.mapping.Mapper;
import org.mongodb.morphia.query.validation.ValidationFailure;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mongodb.morphia.query.FilterOperator.ALL;
import static org.mongodb.morphia.query.FilterOperator.EQUAL;
import static org.mongodb.morphia.query.FilterOperator.EXISTS;
import static org.mongodb.morphia.query.FilterOperator.GEO_WITHIN;
import static org.mongodb.morphia.query.FilterOperator.IN;
import static org.mongodb.morphia.query.FilterOperator.MOD;
import static org.mongodb.morphia.query.FilterOperator.NOT_IN;
import static org.mongodb.morphia.query.FilterOperator.SIZE;
import static org.mongodb.morphia.query.QueryValidator.validateQuery;
public class QueryValidatorTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void shouldAllowAllOperatorForIterableMapAndArrayValues() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, ALL, Arrays.asList(1, 2),
new ArrayList<ValidationFailure>()), is(true));
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, ALL, Collections.emptySet(),
new ArrayList<ValidationFailure>()), is(true));
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, ALL, new HashMap<String, String>(),
new ArrayList<ValidationFailure>()), is(true));
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, ALL, new int[0],
new ArrayList<ValidationFailure>()), is(true));
}
// All of the following tests are whitebox, as I have retrofitted them afterwards. I have no idea if this is the required
// functionality or not
@Test
public void shouldAllowBooleanValuesForExistsOperator() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, EXISTS, true, new ArrayList<ValidationFailure>()),
is(true));
}
@Test
public void shouldAllowGeoWithinOperatorWithAllAppropriateTrimmings() {
// expect
MappedClass mappedClass = new MappedClass(GeoEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("array");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass, mappedField, List.class, GEO_WITHIN, new BasicDBObject("$box", 1),
new ArrayList<ValidationFailure>()), is(true));
}
@Test
public void shouldAllowInOperatorForIterableMapAndArrayValues() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, IN, Arrays.asList(1, 2),
new ArrayList<ValidationFailure>()), is(true));
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, IN, Collections.emptySet(),
new ArrayList<ValidationFailure>()), is(true));
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, IN, new HashMap<String, String>(),
new ArrayList<ValidationFailure>()), is(true));
assertThat(QueryValidator.isCompatibleForOperator(null,
null,
SimpleEntity.class,
IN,
new int[0],
new ArrayList<ValidationFailure>()),
is(true));
}
@Test
public void shouldAllowModOperatorForArrayOfIntegerValues() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, MOD, new int[2],
new ArrayList<ValidationFailure>()), is(true));
}
@Test
public void shouldAllowNotInOperatorForIterableMapAndArrayValues() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, NOT_IN, Arrays.asList(1, 2),
new ArrayList<ValidationFailure>()), is(true));
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, NOT_IN, Collections.emptySet(),
new ArrayList<ValidationFailure>()), is(true));
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, NOT_IN, new HashMap<String, String>(),
new ArrayList<ValidationFailure>()), is(true));
assertThat(QueryValidator.isCompatibleForOperator(null,
null, SimpleEntity.class, NOT_IN, new int[0], new ArrayList<ValidationFailure>()),
is(true));
}
@Test
//this used to fail
public void shouldAllowSizeOperatorForArrayListTypesAndIntegerValues() {
// given
MappedClass mappedClass = new MappedClass(EntityWithListsAndArrays.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("arrayListOfIntegers");
// expect
assertThat(QueryValidator.isCompatibleForOperator(mappedClass,
mappedField,
NullClass.class,
SIZE,
3,
new ArrayList<ValidationFailure>()),
is(true));
}
@Test
public void shouldAllowSizeOperatorForArraysAndIntegerValues() {
// given
MappedClass mappedClass = new MappedClass(EntityWithListsAndArrays.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("arrayOfInts");
// expect
assertThat(QueryValidator.isCompatibleForOperator(mappedClass,
mappedField,
NullClass.class,
SIZE,
3,
new ArrayList<ValidationFailure>()),
is(true));
}
@Test
public void shouldAllowSizeOperatorForListTypesAndIntegerValues() {
// given
MappedClass mappedClass = new MappedClass(EntityWithListsAndArrays.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("listOfIntegers");
// expect
assertThat(QueryValidator.isCompatibleForOperator(mappedClass,
mappedField,
NullClass.class,
SIZE,
3,
new ArrayList<ValidationFailure>()),
is(true));
}
@Test
public void shouldAllowTypeThatMatchesKeyTypeValue() {
// expect
MappedClass mappedClass = new MappedClass(SimpleEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("integer");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass, mappedField, Integer.class, EQUAL,
new Key<Number>(Integer.class, "Integer", new ObjectId()),
new ArrayList<ValidationFailure>()), is(true));
}
@Test
public void shouldAllowValueOfPatternWithTypeOfString() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, String.class, EQUAL, Pattern.compile("."),
new ArrayList<ValidationFailure>()), is(true));
}
@Test
public void shouldAllowValueWithEntityAnnotationAndTypeOfKey() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, Key.class, EQUAL, new SimpleEntity(),
new ArrayList<ValidationFailure>()), is(true));
}
@Test
public void shouldAllowValuesOfIntegerIfTypeIsDouble() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, Double.class, EQUAL, 1, new ArrayList<ValidationFailure>()),
is(true));
assertThat(QueryValidator.isCompatibleForOperator(null, null, double.class, EQUAL, 1, new ArrayList<ValidationFailure>()),
is(true));
}
@Test
public void shouldAllowValuesOfIntegerIfTypeIsInteger() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, int.class, EQUAL, 1, new ArrayList<ValidationFailure>()),
is(true));
assertThat(QueryValidator.isCompatibleForOperator(null, null, Integer.class, EQUAL, 1,
new ArrayList<ValidationFailure>()), is(true));
}
@Test
public void shouldAllowValuesOfIntegerOrLongIfTypeIsLong() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, Long.class, EQUAL, 1, new ArrayList<ValidationFailure>()), is(true));
assertThat(QueryValidator.isCompatibleForOperator(null, null, long.class, EQUAL, 1, new ArrayList<ValidationFailure>()), is(true));
assertThat(QueryValidator.isCompatibleForOperator(null, null, Long.class, EQUAL, 1L, new ArrayList<ValidationFailure>()), is(true));
assertThat(QueryValidator.isCompatibleForOperator(null, null, long.class, EQUAL, 1L, new ArrayList<ValidationFailure>()), is(true));
}
@Test
public void shouldAllowValuesOfList() {
// expect
MappedClass mappedClass = new MappedClass(SimpleEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("name");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass, mappedField, List.class, EQUAL, new ArrayList<String>(),
new ArrayList<ValidationFailure>()), is(true));
}
@Test
public void shouldAllowValuesOfLongIfTypeIsDouble() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, Double.class, EQUAL, 1L, new ArrayList<ValidationFailure>()),
is(true));
assertThat(QueryValidator.isCompatibleForOperator(null, null, double.class, EQUAL, 1L, new ArrayList<ValidationFailure>()),
is(true));
}
@Test
public void shouldBeCompatibleIfTypeIsNull() {
// expect
// frankly not sure we should just let nulls through
assertThat(QueryValidator.isCompatibleForOperator(null, null, null, EQUAL, "value", new ArrayList<ValidationFailure>()), is(true));
}
@Test
public void shouldBeCompatibleIfValueIsNull() {
// expect
// frankly not sure we should just let nulls through
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, EQUAL, null, new ArrayList<ValidationFailure>()),
is(true));
}
@Test
public void shouldNotAllowGeoOperatorIfValueDoesNotContainCorrectField() {
// expect
MappedClass mappedClass = new MappedClass(GeoEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("array");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass, mappedField, List.class, GEO_WITHIN,
new BasicDBObject("name", "value"), new ArrayList<ValidationFailure>()),
is(false));
}
@Test
public void shouldNotAllowGeoOperatorIfValueIsNotDBObject() {
// expect
MappedClass mappedClass = new MappedClass(GeoEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("array");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass, mappedField, List.class, GEO_WITHIN, "value",
new ArrayList<ValidationFailure>()), is(false));
}
@Test
public void shouldNotAllowGeoWithinWhenValueDoesNotContainKeyword() {
// expect
MappedClass mappedClass = new MappedClass(GeoEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("array");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass, mappedField, List.class, GEO_WITHIN,
new BasicDBObject("notValidKey", 1), new ArrayList<ValidationFailure>()),
is(false));
}
@Test
//this used to fail
public void shouldNotAllowModOperatorWithNonArrayValue() {
assertThat(QueryValidator.isCompatibleForOperator(null, null, String.class, MOD, "value", new ArrayList<ValidationFailure>()),
is(false));
}
@Test
public void shouldNotAllowModOperatorWithNonIntegerArray() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, MOD, new String[]{"value"},
new ArrayList<ValidationFailure>()), is(false));
}
@Test
public void shouldNotAllowNonBooleanValuesForExistsOperator() {
// given
MappedClass mappedClass = new MappedClass(SimpleEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("name");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass, mappedField, SimpleEntity.class, EXISTS, "value",
new ArrayList<ValidationFailure>()), is(false));
}
@Test
public void shouldNotAllowNonIntegerTypeIfValueIsInt() {
// expect
MappedClass mappedClass = new MappedClass(SimpleEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("name");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass, mappedField, SimpleEntity.class, EQUAL, 1,
new ArrayList<ValidationFailure>()), is(false));
}
@Test
public void shouldNotAllowNonIntegerValueIfTypeIsInt() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, int.class, EQUAL, "some non int value",
new ArrayList<ValidationFailure>()), is(false));
}
@Test
public void shouldNotAllowNonKeyTypeWithKeyValue() {
// expect
MappedClass mappedClass = new MappedClass(EntityWithListsAndArrays.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("listOfIntegers");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass, mappedField, SimpleEntity.class, EQUAL,
new Key<String>(String.class, "kind", new ObjectId()),
new ArrayList<ValidationFailure>()),
is(false));
}
@Test
//this used to fail
public void shouldNotAllowNonStringTypeWithValueOfPattern() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, Pattern.class, EQUAL, Pattern.compile("."),
new ArrayList<ValidationFailure>()), is(false));
}
@Test
public void shouldNotAllowOtherValuesForAllOperator() {
// given
MappedClass mappedClass = new MappedClass(SimpleEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("name");
// expect
assertThat(QueryValidator.isCompatibleForOperator(mappedClass, mappedField, SimpleEntity.class, ALL, "value",
new ArrayList<ValidationFailure>()), is(false));
}
@Test
public void shouldNotAllowOtherValuesForInOperator() {
// expect
MappedClass mappedClass = new MappedClass(SimpleEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("name");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass,
mappedField,
String.class,
IN,
"value",
new ArrayList<ValidationFailure>()),
is(false));
}
@Test
public void shouldNotAllowOtherValuesForNotInOperator() {
// expect
MappedClass mappedClass = new MappedClass(SimpleEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("name");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass, mappedField, SimpleEntity.class, NOT_IN, "value",
new ArrayList<ValidationFailure>()), is(false));
}
@Test
public void shouldNotAllowSizeOperatorForNonIntegerValues() {
// expect
MappedClass mappedClass = new MappedClass(SimpleEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("name");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass,
mappedField,
ArrayList.class,
SIZE,
"value",
new ArrayList<ValidationFailure>()),
is(false));
}
@Test
public void shouldNotAllowSizeOperatorForNonListTypes() {
// given
MappedClass mappedClass = new MappedClass(EntityWithListsAndArrays.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("notAnArrayOrList");
// expect
assertThat(QueryValidator.isCompatibleForOperator(mappedClass,
mappedField,
NullClass.class,
SIZE,
3,
new ArrayList<ValidationFailure>()),
is(false));
}
@Test
public void shouldNotAllowStringValueWithTypeThatIsNotString() {
// expect
MappedClass mappedClass = new MappedClass(SimpleEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("name");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass, mappedField, Integer.class, EQUAL, "value",
new ArrayList<ValidationFailure>()), is(false));
}
@Test
public void shouldNotAllowTypeThatDoesNotMatchKeyTypeValue() {
// expect
MappedClass mappedClass = new MappedClass(SimpleEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("name");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass, mappedField, String.class, EQUAL,
new Key<Number>(Integer.class, "Integer", new ObjectId()),
new ArrayList<ValidationFailure>()), is(false));
}
@Test
public void shouldNotAllowValueWithoutEntityAnnotationAndTypeOfKey() {
// expect
MappedClass mappedClass = new MappedClass(SimpleEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("name");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass,
mappedField,
Key.class,
EQUAL,
"value",
new ArrayList<ValidationFailure>()),
is(false));
}
@Test
//this used to fail
public void shouldNotErrorIfModOperatorIsUsedWithZeroLengthArrayOfIntegerValues() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, MOD, new int[0],
new ArrayList<ValidationFailure>()), is(false));
}
@Test
//this used to fail
public void shouldNotErrorModOperatorWithArrayOfNullValues() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, SimpleEntity.class, MOD, new String[1],
new ArrayList<ValidationFailure>()), is(false));
}
@Test
public void shouldNotErrorWhenValidateQueryCalledWithNullValue() {
// this unit test is to drive fixing a null pointer in the logging code. It's a bit stupid but it's an edge case that wasn't
// caught.
// when this is called, don't error
validateQuery(SimpleEntity.class, new Mapper(), new StringBuilder("name"), EQUAL, null, true, true);
}
@Test
public void shouldRejectNonDoubleValuesIfTypeIsDouble() {
// expect
assertThat(QueryValidator.isCompatibleForOperator(null, null, Double.class, EQUAL, "Not a double",
new ArrayList<ValidationFailure>()), is(false));
}
@Test
public void shouldRejectTypesAndValuesThatDoNotMatch() {
// expect
MappedClass mappedClass = new MappedClass(SimpleEntity.class, new Mapper());
MappedField mappedField = mappedClass.getMappedField("name");
assertThat(QueryValidator.isCompatibleForOperator(mappedClass, mappedField, String.class, EQUAL, 1,
new ArrayList<ValidationFailure>()), is(false));
}
@Test
public void shouldReferToMappedClassInExceptionWhenFieldNotFound() {
thrown.expect(ValidationException.class);
thrown.expectMessage("The field 'notAField' could not be found in 'org.bson.types.ObjectId'");
validateQuery(SimpleEntity.class, new Mapper(), new StringBuilder("id.notAField"), FilterOperator.EQUAL, 1, true, true);
}
@Test
public void shouldReferToMappedClassInExceptionWhenQueryingPastReferenceField() {
thrown.expect(ValidationException.class);
thrown.expectMessage("Cannot use dot-notation past 'reference' in 'org.mongodb.morphia.query.QueryValidatorTest$WithReference'");
validateQuery(WithReference.class, new Mapper(), new StringBuilder("reference.name"), FilterOperator.EQUAL, "", true, true);
}
@Test
public void shouldReferToMappedClassInExceptionWhenQueryingPastSerializedField() {
thrown.expect(ValidationException.class);
thrown.expectMessage("Cannot use dot-notation past 'serialized' in "
+ "'org.mongodb.morphia.query.QueryValidatorTest$WithSerializedField'");
validateQuery(WithSerializedField.class, new Mapper(), new StringBuilder("serialized.name"), FilterOperator.EQUAL, "", true, true);
}
private static class GeoEntity {
private final int[] array = {1};
}
private static class NullClass {
}
private static class WithReference {
@Reference
private SimpleEntity reference;
}
private static class SerializableClass implements Serializable {
private String name;
}
private static class WithSerializedField {
@Serialized
private SerializableClass serialized;
}
}