package nl.knaw.huygens.alexandria.query;
/*
* #%L
* alexandria-service
* =======
* Copyright (C) 2015 - 2017 Huygens ING (KNAW)
* =======
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import org.assertj.core.data.MapEntry;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import nl.knaw.huygens.alexandria.api.model.ErrorEntity;
import nl.knaw.huygens.alexandria.api.model.search.AlexandriaQuery;
import nl.knaw.huygens.alexandria.api.model.search.QueryField;
import nl.knaw.huygens.alexandria.api.model.search.QueryFunction;
import nl.knaw.huygens.alexandria.config.MockConfiguration;
import nl.knaw.huygens.alexandria.endpoint.EndpointPathResolver;
import nl.knaw.huygens.alexandria.endpoint.LocationBuilder;
import nl.knaw.huygens.alexandria.exception.BadRequestException;
import nl.knaw.huygens.alexandria.storage.frames.AnnotationBodyVF;
import nl.knaw.huygens.alexandria.storage.frames.AnnotationVF;
import nl.knaw.huygens.alexandria.test.AlexandriaTest;
public class AlexandriaQueryParserTest extends AlexandriaTest {
private static final Logger LOG = LoggerFactory.getLogger(AlexandriaQueryParserTest.class);
private AlexandriaQueryParser alexandriaQueryParser = new AlexandriaQueryParser(new LocationBuilder(new MockConfiguration(), new EndpointPathResolver()));
@Test
public void testUnknownFindValueThrowsException() {
AlexandriaQuery aQuery = new AlexandriaQuery();
aQuery.setFind("foobar");
try {
alexandriaQueryParser.parse(aQuery);
fail("AlexandriaQueryParseException expected");
} catch (AlexandriaQueryParseException e) {
LOG.info("error message: {}", e.getMessage());
assertThat(e.getMessage()).contains("foobar");
}
}
@Test
public void testUnknownSortValueThrowsException() {
AlexandriaQuery aQuery = new AlexandriaQuery();
aQuery.setSort("id,huey,dewey,louie");
try {
alexandriaQueryParser.parse(aQuery);
fail("AlexandriaQueryParseException expected");
} catch (AlexandriaQueryParseException e) {
LOG.info("error message: {}", e.getMessage());
softly.assertThat(e.getMessage()).contains("huey");
softly.assertThat(e.getMessage()).contains("dewey");
softly.assertThat(e.getMessage()).contains("louie");
softly.assertThat(e.getMessage()).doesNotContain("unknown field: id");
}
}
@Test
public void testUnknownReturnValueThrowsException() {
AlexandriaQuery aQuery = new AlexandriaQuery();
aQuery.setReturns("id,huey,dewey,louie");
try {
alexandriaQueryParser.parse(aQuery);
fail("AlexandriaQueryParseException expected");
} catch (AlexandriaQueryParseException e) {
LOG.info("error message: {}", e.getMessage());
softly.assertThat(e.getMessage()).contains("huey");
softly.assertThat(e.getMessage()).contains("dewey");
softly.assertThat(e.getMessage()).contains("louie");
softly.assertThat(e.getMessage()).doesNotContain("unknown field: id");
}
}
@Test
public void testReturnFields() {
AlexandriaQuery aQuery = new AlexandriaQuery();
aQuery.setFind("annotation");
aQuery.setReturns("id, resource.id, subresource.id");
ParsedAlexandriaQuery paq = alexandriaQueryParser.parse(aQuery);
assertThat(paq.getReturnFields()).containsExactly("id", "resource.id", "subresource.id");
assertThat(paq.getFieldsToGroup()).isEmpty();
}
@Test
public void testReturnFieldsWithList() {
AlexandriaQuery aQuery = new AlexandriaQuery();
aQuery.setFind("annotation");
aQuery.setReturns("list(id), resource.id, resource.url");
ParsedAlexandriaQuery paq = alexandriaQueryParser.parse(aQuery);
assertThat(paq.getReturnFields()).containsExactly("id", "resource.id", "resource.url");
assertThat(paq.getFieldsToGroup()).containsExactly("id");
Map<String, Object> map = ImmutableMap.of("id", "Id", "resource.id", "Resource.id", "resource.url", "Resource.URL");
assertThat(paq.concatenateGroupByFieldsValues(map)).isEqualTo("Resource.idResource.URL");
Map<String, Object> map1 = new HashMap<>();
map1.put("id", "Id1");
map1.put("resource.id", "Resource.id");
map1.put("resource.url", "Resource.URL");
Map<String, Object> map2 = ImmutableMap.of("id", "Id2", "resource.id", "Resource.id", "resource.url", "Resource.URL");
Map<String, Object> map3 = ImmutableMap.of("id", "Id3", "resource.id", "Resource.id", "resource.url", "Resource.URL");
List<Map<String, Object>> mapList = ImmutableList.of(map1, map2, map3);
Map<String, Object> expected = ImmutableMap.of(//
"_list", ImmutableList.of(ImmutableMap.of("id", "Id1"), ImmutableMap.of("id", "Id2"), ImmutableMap.of("id", "Id3")), //
"resource.id", "Resource.id", //
"resource.url", "Resource.URL"//
);
assertThat(paq.collectListFieldValues(mapList)).containsAllEntriesOf(expected);
}
@Test
public void testGenerateSortTokenFromString1() {
SortToken st1 = AlexandriaQueryParser.sortToken("id");
softly.assertThat(st1.isAscending()).isTrue();
softly.assertThat(st1.getField()).isEqualTo(QueryField.id);
}
@Test
public void testGenerateSortTokenFromString2() {
SortToken st1 = AlexandriaQueryParser.sortToken("+when");
softly.assertThat(st1.isAscending()).isTrue();
softly.assertThat(st1.getField()).isEqualTo(QueryField.when);
}
@Test
public void testGenerateSortTokenFromString3() {
SortToken st1 = AlexandriaQueryParser.sortToken("-type");
softly.assertThat(st1.isAscending()).isFalse();
softly.assertThat(st1.getField()).isEqualTo(QueryField.type);
}
@Test
public void testWhereTokenization() {
String where = "type:eq(\"Tag\")"//
+ " who:eq(\"nederlab\")"//
+ " state:eq(\"CONFIRMED\")"//
+ " resource.id:inSet(\"11111-111-111-11-111\",\"11111-111-111-11-112\")";
List<WhereToken> tokens = alexandriaQueryParser.tokenize(where);
LOG.info("errors:{}", alexandriaQueryParser.parseErrors);
assertThat(tokens).hasSize(4);
WhereToken typeToken = tokens.get(0);
softly.assertThat(typeToken.getProperty()).isEqualTo(QueryField.type);
softly.assertThat(typeToken.getFunction()).isEqualTo(QueryFunction.eq);
softly.assertThat(typeToken.getParameters()).containsExactly("Tag");
WhereToken whoToken = tokens.get(1);
softly.assertThat(whoToken.getProperty()).isEqualTo(QueryField.who);
softly.assertThat(whoToken.getFunction()).isEqualTo(QueryFunction.eq);
softly.assertThat(whoToken.getParameters()).containsExactly("nederlab");
WhereToken stateToken = tokens.get(2);
softly.assertThat(stateToken.getProperty()).isEqualTo(QueryField.state);
softly.assertThat(stateToken.getFunction()).isEqualTo(QueryFunction.eq);
softly.assertThat(stateToken.getParameters()).containsExactly("CONFIRMED");
WhereToken resourceToken = tokens.get(3);
softly.assertThat(resourceToken.getProperty()).isEqualTo(QueryField.resource_id);
softly.assertThat(resourceToken.getFunction()).isEqualTo(QueryFunction.inSet);
softly.assertThat(resourceToken.getParameters()).containsExactly("11111-111-111-11-111", "11111-111-111-11-112");
}
@Test
public void testTokenizingWhereClauseWithMissingQuoteAddsParseError() {
String where = "type:eq(\"Tag)";
List<WhereToken> tokens = alexandriaQueryParser.tokenize(where);
List<String> parseErrors = alexandriaQueryParser.parseErrors;
LOG.info("errors:{}", parseErrors);
softly.assertThat(parseErrors).isNotEmpty();
softly.assertThat(tokens).isEmpty();
}
@Test
public void testTokenizingWhereClauseWithIllegalFunctionAddsParseError() {
String where = "type:not(1)";
List<WhereToken> tokens = alexandriaQueryParser.tokenize(where);
List<String> parseErrors = alexandriaQueryParser.parseErrors;
LOG.info("errors:{}", parseErrors);
softly.assertThat(parseErrors).isNotEmpty();
assertThat(tokens).isEmpty();
}
@Test
public void testPredicateForWhoEq() {
WhereToken whereToken = new WhereToken(QueryField.who, QueryFunction.eq, ImmutableList.of("Gremlin"));
AnnotationVF passingAnnotationVF = mock(AnnotationVF.class);
when(passingAnnotationVF.getProvenanceWho()).thenReturn("Gremlin");
AnnotationVF failingAnnotationVF = mock(AnnotationVF.class);
when(failingAnnotationVF.getProvenanceWho()).thenReturn("SomeoneElse");
Predicate<AnnotationVF> predicate = AlexandriaQueryParser.toPredicate(whereToken);
assertThat(predicate.test(passingAnnotationVF)).isTrue();
assertThat(predicate.test(failingAnnotationVF)).isFalse();
}
@Test
public void testPredicateForTypeEq() {
WhereToken whereToken = new WhereToken(QueryField.type, QueryFunction.eq, ImmutableList.of("Tag"));
AnnotationVF passingAnnotationVF = mock(AnnotationVF.class);
when(passingAnnotationVF.getType()).thenReturn("Tag");
AnnotationVF failingAnnotationVF = mock(AnnotationVF.class);
when(failingAnnotationVF.getType()).thenReturn("Whatever");
Predicate<AnnotationVF> predicate = AlexandriaQueryParser.toPredicate(whereToken);
assertThat(predicate.test(passingAnnotationVF)).isTrue();
assertThat(predicate.test(failingAnnotationVF)).isFalse();
}
@Test
public void testPredicateForStateEq() {
WhereToken whereToken = new WhereToken(QueryField.state, QueryFunction.eq, ImmutableList.of("CONFIRMED"));
AnnotationVF passingAnnotationVF = mock(AnnotationVF.class);
when(passingAnnotationVF.getState()).thenReturn("CONFIRMED");
AnnotationVF failingAnnotationVF = mock(AnnotationVF.class);
when(failingAnnotationVF.getState()).thenReturn("TENTATIVE");
Predicate<AnnotationVF> predicate = AlexandriaQueryParser.toPredicate(whereToken);
softly.assertThat(predicate.test(passingAnnotationVF)).isTrue();
softly.assertThat(predicate.test(failingAnnotationVF)).isFalse();
}
@Test
public void testPredicateForValueMatch() {
WhereToken whereToken = new WhereToken(QueryField.value, QueryFunction.match, ImmutableList.of("super.*"));
AnnotationVF passingAnnotationVF = mock(AnnotationVF.class);
when(passingAnnotationVF.getValue()).thenReturn("supergirl");
AnnotationVF passingAnnotationVF2 = mock(AnnotationVF.class);
when(passingAnnotationVF2.getValue()).thenReturn("superman");
AnnotationVF failingAnnotationVF = mock(AnnotationVF.class);
when(failingAnnotationVF.getValue()).thenReturn("batman");
Predicate<AnnotationVF> predicate = AlexandriaQueryParser.toPredicate(whereToken);
softly.assertThat(predicate.test(passingAnnotationVF)).isTrue();
softly.assertThat(predicate.test(passingAnnotationVF2)).isTrue();
softly.assertThat(predicate.test(failingAnnotationVF)).isFalse();
}
@Test //
public void testPredicateForWhenInRange() {
WhereToken whereToken = new WhereToken(QueryField.when, QueryFunction.inRange, ImmutableList.of("20150101", "20151231"));
AnnotationVF passingAnnotationVF = mock(AnnotationVF.class);
when(passingAnnotationVF.getProvenanceWhen()).thenReturn("20150615");
AnnotationVF failingAnnotationVF = mock(AnnotationVF.class);
when(failingAnnotationVF.getProvenanceWhen()).thenReturn("20160101");
Predicate<AnnotationVF> predicate = AlexandriaQueryParser.toPredicate(whereToken);
assertThat(predicate.test(passingAnnotationVF)).isTrue();
assertThat(predicate.test(failingAnnotationVF)).isFalse();
}
@Test
public void testUserStory4a() {
String userId = "USERID";
AlexandriaQuery aQuery = new AlexandriaQuery();
aQuery.setFind("annotation");
aQuery.setWhere(//
"type:eq(\"Tag\")"//
+ " who:eq(\"" + userId + "\")"//
+ " state:eq(\"CONFIRMED\")"//
);
aQuery.setSort("when");
aQuery.setReturns("when,value,resource.id,subresource.id");
ParsedAlexandriaQuery paq = alexandriaQueryParser.parse(aQuery);
AnnotationVF passAnnotation = mock(AnnotationVF.class);
AnnotationBodyVF passBody = mock(AnnotationBodyVF.class);
when(passBody.getType()).thenReturn("Tag");
// .put(QueryField.resource_id, AnnotationVF::getResourceId)//
// .put("subresource.id", AnnotationVF::getSubResourceId)//
when(passAnnotation.getUuid()).thenReturn("uuid");
when(passAnnotation.getBody()).thenReturn(passBody);
when(passAnnotation.getType()).thenReturn("Tag");
when(passAnnotation.getValue()).thenReturn("Value");
when(passAnnotation.getProvenanceWhen()).thenReturn("1");
when(passAnnotation.getProvenanceWho()).thenReturn(userId);
when(passAnnotation.getProvenanceWhy()).thenReturn("because");
when(passAnnotation.getResourceId()).thenReturn("resourceId");
when(passAnnotation.getSubResourceId()).thenReturn("subresourceId");
when(passAnnotation.getState()).thenReturn("CONFIRMED");
AnnotationVF failAnnotation = mock(AnnotationVF.class);
when(failAnnotation.getBody()).thenReturn(passBody);
when(failAnnotation.getProvenanceWhen()).thenReturn("2");
when(failAnnotation.getProvenanceWho()).thenReturn(userId);
when(failAnnotation.getState()).thenReturn("TENTATIVE");
when(failAnnotation.getType()).thenReturn("Tag");
// find: test VFClass
assertThat(paq.getVFClass()).isEqualTo(AnnotationVF.class);
// test predicate
Predicate<AnnotationVF> predicate = paq.getPredicate();
LOG.info("predicate={}", predicate);
assertThat(predicate).isNotNull();
assertThat(predicate.test(passAnnotation)).isTrue();
assertThat(predicate.test(failAnnotation)).isFalse();
// sort: test resultComparator
Comparator<AnnotationVF> resultComparator = paq.getResultComparator();
int compare1 = resultComparator.compare(passAnnotation, failAnnotation);
assertThat(compare1).isEqualTo(-1);
int compare2 = resultComparator.compare(failAnnotation, passAnnotation);
assertThat(compare2).isEqualTo(1);
int compare3 = resultComparator.compare(passAnnotation, passAnnotation);
assertThat(compare3).isEqualTo(0);
// return: test returnFields + resultMapper
assertThat(paq.getReturnFields()).containsExactly("when", "value", "resource.id", "subresource.id");
Function<AnnotationVF, Map<String, Object>> resultMapper = paq.getResultMapper();
Map<String, Object> resultMap = resultMapper.apply(passAnnotation);
assertThat(resultMap).contains( //
MapEntry.entry("when", "1"), //
MapEntry.entry("value", "Value"), //
MapEntry.entry("resource.id", "resourceId"), //
MapEntry.entry("subresource.id", "subresourceId") //
);
}
AtomicInteger alwaysTrueCalled = new AtomicInteger(0);
Predicate<Object> alwaysTrue = o -> {
int times = alwaysTrueCalled.incrementAndGet();
LOG.info("alwaysTrue called {} times", times);
return true;
};
AtomicInteger alwaysFalseCalled = new AtomicInteger(0);
Predicate<Object> alwaysFalse = o -> {
int times = alwaysFalseCalled.incrementAndGet();
LOG.info("alwaysFalse called {} times", times);
return false;
};
@Test
public void testPredicates() {
List<Predicate<Object>> list = new ArrayList<>();
list.add(alwaysTrue);
list.add(alwaysTrue);
list.add(alwaysFalse);
list.add(alwaysFalse);
list.add(alwaysFalse);
list.add(alwaysFalse);
list.add(alwaysTrue);
list.add(alwaysTrue);
list.add(alwaysTrue);
Predicate<Object> combination = list.stream()//
.reduce(alwaysTrue, (p, np) -> p = p.and(np));
softly.assertThat(combination.test("whtvr")).isFalse();
softly.assertThat(alwaysTrueCalled.get()).isEqualTo(3);
softly.assertThat(alwaysFalseCalled.get()).isEqualTo(1);
// alwaysTrueCalled = new AtomicInteger(0);
// alwaysFalseCalled = new AtomicInteger(0);
// Predicate<Object> parallel = list.parallelStream()//
// .reduce(alwaysTrue, (p, np) -> p = p.and(np));
//
// assertThat(parallel.test("whtvr")).isFalse();
// assertThat(alwaysTrueCalled.get()).isEqualTo(5);
// assertThat(alwaysFalseCalled.get()).isEqualTo(1);
}
@Test
public void testGetAnnotationIdFromDeprecatedAnnotationRemovesRevisionNumber() {
AnnotationVF avf = mock(AnnotationVF.class);
String randomUUID = UUID.randomUUID().toString();
when(avf.getUuid()).thenReturn(randomUUID + ".0");
String annotationId = AlexandriaQueryParser.getAnnotationId(avf);
assertThat(annotationId).isEqualTo(randomUUID);
}
@Test
public void testInvalidStateInStateEqThrowsBadRequestException() {
WhereToken whereToken = new WhereToken(QueryField.state, QueryFunction.eq, ImmutableList.of("RUBBISH"));
try {
AlexandriaQueryParser.toPredicate(whereToken);
fail("expected BadRequestException");
} catch (BadRequestException e) {
String responseMessage = ((ErrorEntity) e.getResponse().getEntity()).getMessage();
assertThat(responseMessage).isEqualTo("RUBBISH is not a valid value for state");
}
}
@Test
public void testOneInvalidStatesInStateInSetThrowsBadRequestException() {
WhereToken whereToken = new WhereToken(QueryField.state, QueryFunction.eq, ImmutableList.of("RUBBISH", "CONFIRMED", "TENTATIVE"));
try {
AlexandriaQueryParser.toPredicate(whereToken);
fail("expected BadRequestException");
} catch (BadRequestException e) {
String responseMessage = ((ErrorEntity) e.getResponse().getEntity()).getMessage();
assertThat(responseMessage).isEqualTo("RUBBISH is not a valid value for state");
}
}
@Test
public void testTwoInvalidStatesInStateInSetThrowsBadRequestException() {
WhereToken whereToken = new WhereToken(QueryField.state, QueryFunction.eq, ImmutableList.of("RUBBISH", "GARBAGE", "CONFIRMED"));
try {
AlexandriaQueryParser.toPredicate(whereToken);
fail("expected BadRequestException");
} catch (BadRequestException e) {
String responseMessage = ((ErrorEntity) e.getResponse().getEntity()).getMessage();
assertThat(responseMessage).isEqualTo("RUBBISH, GARBAGE are not valid values for state");
}
}
}