blob: 769f16878e6ff48b66c744660417b37e06747940 [file] [log] [blame]
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you 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.apache.sling.graphql.core.engine;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.sling.graphql.api.SchemaProvider;
import org.apache.sling.graphql.api.SelectedField;
import org.apache.sling.graphql.api.SelectionSet;
import org.apache.sling.graphql.api.SlingDataFetcher;
import org.apache.sling.graphql.api.SlingGraphQLException;
import org.apache.sling.graphql.api.engine.QueryExecutor;
import org.apache.sling.graphql.api.engine.ValidationResult;
import org.apache.sling.graphql.core.mocks.CharacterTypeResolver;
import org.apache.sling.graphql.core.mocks.DigestDataFetcher;
import org.apache.sling.graphql.core.mocks.DroidDTO;
import org.apache.sling.graphql.core.mocks.DummyTypeResolver;
import org.apache.sling.graphql.core.mocks.EchoDataFetcher;
import org.apache.sling.graphql.core.mocks.FailingDataFetcher;
import org.apache.sling.graphql.core.mocks.HumanDTO;
import org.apache.sling.graphql.core.mocks.MockSchemaProvider;
import org.apache.sling.graphql.core.mocks.TestUtil;
import org.apache.sling.graphql.core.schema.RankedSchemaProviders;
import org.apache.sling.testing.mock.osgi.MockOsgi;
import org.junit.Test;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import graphql.schema.idl.TypeDefinitionRegistry;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
public class DefaultQueryExecutorTest extends ResourceQueryTestBase {
protected void setupAdditionalServices() {
final Dictionary<String, Object> staticData = new Hashtable<>();
staticData.put("test", true);
HumanDTO human = new HumanDTO("human-1", "Luke", "Tatooine");
DroidDTO droid = new DroidDTO("droid-1", "R2-D2", "whistle");
final List<Object> characters = new ArrayList<>();
characters.add(human);
characters.add(droid);
final Dictionary<String, Object> data = new Hashtable<>();
data.put("characters", characters);
TestUtil.registerSlingTypeResolver(context.bundleContext(), "character/resolver", new CharacterTypeResolver());
TestUtil.registerSlingDataFetcher(context.bundleContext(), "character/fetcher", new EchoDataFetcher(data));
TestUtil.registerSlingDataFetcher(context.bundleContext(), "echoNS/echo", new EchoDataFetcher(null));
TestUtil.registerSlingDataFetcher(context.bundleContext(), "failure/fail", new FailingDataFetcher());
TestUtil.registerSlingDataFetcher(context.bundleContext(), "test/static", new EchoDataFetcher(staticData));
TestUtil.registerSlingDataFetcher(context.bundleContext(), "test/fortyTwo", new EchoDataFetcher(42));
TestUtil.registerSlingDataFetcher(context.bundleContext(), "sling/digest", new DigestDataFetcher());
final Dictionary<String, Object> dataCombined = new Hashtable<>();
dataCombined.put("unionTest", characters);
dataCombined.put("interfaceTest", characters);
TestUtil.registerSlingDataFetcher(context.bundleContext(), "combined/fetcher", new EchoDataFetcher(dataCombined));
}
@Test
public void basicTest() throws Exception {
final String json = queryJSON("{ currentResource { path resourceType } }");
assertThat(json, hasJsonPath("$.data.currentResource"));
assertThat(json, hasJsonPath("$.data.currentResource.path", equalTo(resource.getPath())));
assertThat(json, hasJsonPath("$.data.currentResource.resourceType", equalTo(resource.getResourceType())));
}
@Test
public void staticContentTest() throws Exception {
final String json = queryJSON("{ staticContent { test } }");
assertThat(json, hasJsonPath("$.data.staticContent"));
assertThat(json, hasJsonPath("$.data.staticContent.test", equalTo(true)));
}
@Test
public void digestFieldsTest() throws Exception {
final String json = queryJSON("{ currentResource { path pathMD5 pathSHA256 resourceTypeMD5 } }");
final String pathMD5 = DigestDataFetcher.computeDigest("md5", resource.getPath());
final String pathSHA256 = DigestDataFetcher.computeDigest("sha-256", resource.getPath());
final String resourceTypeMD5 = DigestDataFetcher.computeDigest("md5", resource.getResourceType());
assertThat(json, hasJsonPath("$.data.currentResource"));
assertThat(json, hasJsonPath("$.data.currentResource.path", equalTo(resource.getPath())));
assertThat(json, hasJsonPath("$.data.currentResource.pathMD5", equalTo("md5#path#" + pathMD5)));
assertThat(json, hasJsonPath("$.data.currentResource.pathSHA256", equalTo("sha-256#path#" + pathSHA256)));
assertThat(json, hasJsonPath("$.data.currentResource.resourceTypeMD5", equalTo("md5#resourceType#" + resourceTypeMD5)));
}
@Test
public void nullValueTest() throws Exception {
final String json = queryJSON("{ currentResource { nullValue } }");
assertThat(json, hasJsonPath("$.data.currentResource"));
assertThat(json, hasJsonPath("$.data.currentResource.nullValue", is(nullValue())));
}
@Test
public void queryValidationErrorResponseTest() throws Exception {
final String json = queryJSON("{ currentResource_NON_EXISTENT { nullValue } }");
assertThat(json, hasJsonPath("$.errors[0].message"));
assertThat(json, hasJsonPath("$.errors[0].locations"));
assertThat(json, hasJsonPath("$.errors[0].extensions"));
assertThat(json, hasJsonPath("$.errors[0].extensions.classification", is("ValidationError")));
}
@Test
public void dataFetcherFailureTest() {
try {
final String stmt = "{ currentResource { failure } }";
QueryExecutor queryExecutor = context.getService(QueryExecutor.class);
assertNotNull(queryExecutor);
queryExecutor.execute(stmt, Collections.emptyMap(), resource, new String[] {});
} catch(RuntimeException rex) {
assertThat(rex.getMessage(), containsString("Error: type=DataFetchingException; message=Exception while fetching data (/currentResource/failure) : FailingDataFetcher"));
}
}
@Test
public void invalidQueryTest() {
final String stmt = "{ currentRsrc { failure } }";
QueryExecutor queryExecutor = context.getService(QueryExecutor.class);
assertNotNull(queryExecutor);
ValidationResult result = queryExecutor.validate(stmt, Collections.emptyMap(), resource, new String[] {});
assertFalse(result.isValid());
String errors = String.join("\n", result.getErrors());
assertTrue(errors.contains("Error: type=ValidationError; message=Validation error of type FieldUndefined: Field 'currentRsrc' in type 'Query' is undefined @ 'currentRsrc'; location=1,3;"));
}
@Test
public void schemaSelectorsTest(){
final String [] selectors = { "selected", "foryou" };
final String json = queryJSON("{ currentResource { path fortyTwo } }", selectors);
assertThat(json, hasJsonPath("$.data.currentResource"));
assertThat(json, hasJsonPath("$.data.currentResource.path", equalTo(42)));
assertThat(json, hasJsonPath("$.data.currentResource.fortyTwo", equalTo(42)));
}
@Test
public void invalidFetcherNamesTest() {
context.registerService(SchemaProvider.class, new MockSchemaProvider("failing-fetcher-schema"), Constants.SERVICE_RANKING,
Integer.MAX_VALUE);
final ServiceRegistration<?> reg = TestUtil.registerSlingDataFetcher(context.bundleContext(), "missingSlash", new EchoDataFetcher(42));
try {
final String json = queryJSON("{ currentResource { missingSlash } }", new String[] {});
assertThat(json, hasJsonPath("$.errors[0].message", containsString("Invalid fetcher name missingSlash")));
assertThat(json, hasJsonPath("$.errors[0].extensions.exception", is(SlingGraphQLException.class.getName())));
} finally {
reg.unregister();
}
}
@Test
public void invalidTypeResolverNamesTest() {
context.registerService(SchemaProvider.class, new MockSchemaProvider("failing-type-resolver-schema"), Constants.SERVICE_RANKING,
Integer.MAX_VALUE);
final ServiceRegistration<?> reg = TestUtil.registerSlingTypeResolver(context.bundleContext(), "missingSlash",
new DummyTypeResolver());
try {
final String json = queryJSON("{ currentResource { missingSlash } }", new String[] {});
assertThat(json, hasJsonPath("$.errors[0].message", containsString("Invalid type resolver name missingSlash")));
assertThat(json, hasJsonPath("$.errors[0].extensions.exception", is(SlingGraphQLException.class.getName())));
} finally {
reg.unregister();
}
}
@Test
public void unionQueryTest() throws Exception {
final String json = queryJSON("{ unionQuery { characters { ... on Human { name address } ... on Droid { name primaryFunction } } } }");
assertThat(json, hasJsonPath("$.data.unionQuery"));
assertThat(json, hasJsonPath("$.data.unionQuery.characters[0].name", equalTo("Luke")));
assertThat(json, hasJsonPath("$.data.unionQuery.characters[0].address", equalTo("Tatooine")));
assertThat(json, hasJsonPath("$.data.unionQuery.characters[1].name", equalTo("R2-D2")));
assertThat(json, hasJsonPath("$.data.unionQuery.characters[1].primaryFunction", equalTo("whistle")));
}
@Test
public void interfaceQueryTest() throws Exception {
final String json = queryJSON("{ interfaceQuery { characters { id ... on Human { name address } ... on Droid { name primaryFunction } } } }");
assertThat(json, hasJsonPath("$.data.interfaceQuery"));
assertThat(json, hasJsonPath("$.data.interfaceQuery.characters[0].id", equalTo("human-1")));
assertThat(json, hasJsonPath("$.data.interfaceQuery.characters[0].name", equalTo("Luke")));
assertThat(json, hasJsonPath("$.data.interfaceQuery.characters[0].address", equalTo("Tatooine")));
assertThat(json, hasJsonPath("$.data.interfaceQuery.characters[1].id", equalTo("droid-1")));
assertThat(json, hasJsonPath("$.data.interfaceQuery.characters[1].name", equalTo("R2-D2")));
assertThat(json, hasJsonPath("$.data.interfaceQuery.characters[1].primaryFunction", equalTo("whistle")));
}
@Test
public void selectionSetTest() throws Exception {
queryJSON("{ combinedFetcher { boolValue resourcePath aTest { boolValue test resourcePath } allTests { boolValue test resourcePath } unionTest { ... on Human { id address } ... on Droid { id primaryFunction } } interfaceTest { id ... on Human { address } ... on Droid { primaryFunction } } } }");
// retrieve the service used
ServiceReference<?>[] serviceReferences = context.bundleContext().getServiceReferences(SlingDataFetcher.class.getName(), "(name=combined/fetcher)");
EchoDataFetcher echoDataFetcher = (EchoDataFetcher) context.bundleContext().getService(serviceReferences[0]);
// Access the computed SelectionSet
SelectionSet selectionSet = echoDataFetcher.getSelectionSet();
assertEquals(6, selectionSet.getFields().size());
String[] expectedFieldNames = new String[] {
"boolValue",
"resourcePath",
"aTest",
"unionTest",
"interfaceTest"
};
final List<SelectedField> selectionSetFields = selectionSet.getFields();
for (String expectedFieldname : expectedFieldNames) {
assertTrue(selectionSetFields.stream().anyMatch(f -> expectedFieldname.equals(f.getName())));
}
// Assert it contains the expected results
String[] expectedQualifiedName = new String[] {
"boolValue",
"resourcePath",
"aTest",
"aTest/test",
"aTest/boolValue",
"aTest/resourcePath",
"allTests",
"allTests/test",
"allTests/boolValue",
"allTests/resourcePath",
"unionTest",
"unionTest/Human",
"unionTest/Human/id",
"unionTest/Human/address",
"unionTest/Droid",
"unionTest/Droid/id",
"unionTest/Droid/primaryFunction",
"interfaceTest",
"interfaceTest/id",
"interfaceTest/Human",
"interfaceTest/Human/address",
"interfaceTest/Droid",
"interfaceTest/Droid/primaryFunction"
};
for (String expectedQN : expectedQualifiedName) {
assertTrue(selectionSet.contains(expectedQN));
}
String[] expectedNonInlineQNs = new String[] {
"boolValue",
"resourcePath",
"aTest",
"aTest/test",
"aTest/boolValue",
"aTest/resourcePath",
"allTests",
"allTests/test",
"allTests/boolValue",
"allTests/resourcePath",
"unionTest",
"unionTest/Human/id",
"unionTest/Human/address",
"unionTest/Droid/id",
"unionTest/Droid/primaryFunction",
"interfaceTest",
"interfaceTest/id",
"interfaceTest/Human/address",
"interfaceTest/Droid/primaryFunction"
};
for (String expectedNonInlineQN : expectedNonInlineQNs) {
assertFalse(Objects.requireNonNull(selectionSet.get(expectedNonInlineQN)).isInline());
}
String[] expectedInlineQNs = new String[] {
"unionTest/Human",
"unionTest/Droid",
"interfaceTest/Human",
"interfaceTest/Droid"
};
for (String expectedInlineQN : expectedInlineQNs) {
assertTrue(Objects.requireNonNull(selectionSet.get(expectedInlineQN)).isInline());
}
String[] expectedSubFieldNames = new String[] {
"test",
"boolValue",
"resourcePath"
};
SelectedField allTests = selectionSet.get("allTests");
assert allTests != null;
List<SelectedField> subSelectedFields = allTests.getSubSelectedFields();
for (String expectedSubFieldname : expectedSubFieldNames) {
assertTrue(subSelectedFields.stream().anyMatch(f -> expectedSubFieldname.equals(f.getName())));
}
}
@Test
public void testCachedTypeRegistry() throws IOException {
// by default we'll get the test-schema
final DefaultQueryExecutor queryExecutor = (DefaultQueryExecutor) context.getService(QueryExecutor.class);
final RankedSchemaProviders schemaProvider = context.getService(RankedSchemaProviders.class);
assertNotNull(queryExecutor);
assertNotNull(schemaProvider);
String[] selectors = new String[]{};
TypeDefinitionRegistry
registry1 = queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
TypeDefinitionRegistry registry2 =
queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
assertEquals(registry1, registry2);
// change the schema provider
context.registerService(SchemaProvider.class, new MockSchemaProvider("test-schema-selected-foryou"), Constants.SERVICE_RANKING,
Integer.MAX_VALUE);
TypeDefinitionRegistry
registry3 = queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
TypeDefinitionRegistry registry4 =
queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
assertEquals(registry1, registry2);
assertEquals(registry3, registry4);
assertNotEquals(registry1, registry3);
}
@Test
public void testTypeRegistryWithTheCacheDisabled() throws IOException {
Map<String, Object> properties = new HashMap<>();
properties.put("schemaCacheSize", 0);
DefaultQueryExecutor queryExecutor = (DefaultQueryExecutor) context.registerService(QueryExecutor.class, new DefaultQueryExecutor(),
properties);
MockOsgi.injectServices(queryExecutor, context.bundleContext(), properties);
MockOsgi.activate(queryExecutor, context.bundleContext(), properties);
final RankedSchemaProviders schemaProvider = context.getService(RankedSchemaProviders.class);
assertNotNull(queryExecutor);
assertNotNull(schemaProvider);
String[] selectors = new String[]{};
TypeDefinitionRegistry
registry1 = queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
TypeDefinitionRegistry registry2 =
queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
assertNotEquals(registry1, registry2);
// change the schema provider
context.registerService(SchemaProvider.class, new MockSchemaProvider("test-schema-selected-foryou"), Constants.SERVICE_RANKING,
Integer.MAX_VALUE);
TypeDefinitionRegistry
registry3 = queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
TypeDefinitionRegistry registry4 =
queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
assertNotEquals(registry3, registry4);
}
}