blob: 005274dd6dcd6298d135b8192a60a59bd0774d4a [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.schema.aggregator.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.util.Optional;
import java.util.stream.Stream;
import org.apache.commons.io.IOUtils;
import org.apache.sling.graphql.schema.aggregator.U;
import org.junit.Before;
import org.junit.Test;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import graphql.language.TypeDefinition;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class DefaultSchemaAggregatorTest {
private DefaultSchemaAggregator dsa;
private ProviderBundleTracker tracker;
private BundleContext bundleContext;
private void assertOutput(String expectedResourceName, String actual) throws IOException {
try(InputStream is = getClass().getResourceAsStream(expectedResourceName)) {
assertNotNull("Expecting classpath resource to be present:" + expectedResourceName, is);
final String expected = IOUtils.toString(is, "UTF-8").trim();
assertEquals(expected, actual);
}
}
@Before
public void setup() throws Exception {
dsa = new DefaultSchemaAggregator();
final Field f = dsa.getClass().getDeclaredField("tracker");
f.setAccessible(true);
bundleContext = mock(BundleContext.class);
when(bundleContext.getBundle()).thenReturn(mock(Bundle.class));
tracker = new ProviderBundleTracker();
tracker.activate(bundleContext);
f.set(dsa, tracker);
}
private void assertContainsIgnoreCase(String substring, String source) {
assertTrue("Expecting '" + substring + "' in source string ", source.toLowerCase().contains(substring.toLowerCase()));
}
@Test
public void noProviders() {
final StringWriter target = new StringWriter();
final IOException iox = assertThrows(IOException.class, () -> dsa.aggregate(target, "Aprov", "Bprov"));
assertContainsIgnoreCase("missing providers", iox.getMessage());
assertContainsIgnoreCase("Aprov", iox.getMessage());
assertContainsIgnoreCase("Bprov", iox.getMessage());
assertContainsIgnoreCase("schema aggregated by DefaultSchemaAggregator", target.toString());
}
@Test
public void severalProviders() throws Exception{
final StringWriter target = new StringWriter();
tracker.addingBundle(U.mockProviderBundle(bundleContext, "A", 1, "a1.txt", "a2.z.w.txt", "a3abc.txt", "a4abc.txt"), null);
tracker.addingBundle(U.mockProviderBundle(bundleContext, "B", 2, "b1a.txt", "b2.xy.txt"), null);
dsa.aggregate(target, "b1a", "b2.xy", "a2.z.w");
final String sdl = target.toString().trim();
assertContainsIgnoreCase("schema aggregated by DefaultSchemaAggregator", sdl);
assertOutput("/partials/several-providers-output.txt", sdl);
}
@Test
public void regexpSelection() throws Exception {
final StringWriter target = new StringWriter();
tracker.addingBundle(U.mockProviderBundle(bundleContext, "A", 1, "a.authoring.1.txt", "a.authoring.2.txt", "a.txt", "b.txt"), null);
tracker.addingBundle(U.mockProviderBundle(bundleContext, "B", 2, "b1.txt", "b.authoring.txt"), null);
dsa.aggregate(target, "b1", "/.*\\.authoring.*/");
assertContainsIgnoreCase("schema aggregated by DefaultSchemaAggregator", target.toString());
U.assertPartialsFoundInSchema(target.toString(), "a.authoring.1", "a.authoring.2", "b.authoring", "b1");
}
@Test
public void verifyResultSyntax() throws Exception {
final StringWriter target = new StringWriter();
tracker.addingBundle(U.mockProviderBundle(bundleContext, "SDL", 1, "a.sdl.txt", "b.sdl.txt", "c.sdl.txt"), null);
dsa.aggregate(target, "/.*/");
// Parse the output with a real SDL parser
final String sdl = target.toString();
final TypeDefinitionRegistry reg = new SchemaParser().parse(sdl);
// And make sure it contains what we expect
assertTrue(reg.getDirectiveDefinition("fetcher").isPresent());
assertTrue(reg.getType("SlingResourceConnection").isPresent());
assertTrue(reg.getType("PageInfo").isPresent());
final Optional<TypeDefinition> query = reg.getType("Query");
assertTrue("Expecting Query", query.isPresent());
assertTrue(query.get().getChildren().toString().contains("oneSchemaResource"));
assertTrue(query.get().getChildren().toString().contains("oneSchemaQuery"));
final Optional<TypeDefinition> mutation = reg.getType("Mutation");
assertTrue("Expecting Mutation", mutation.isPresent());
assertTrue(mutation.get().getChildren().toString().contains("someMutation"));
assertOutput("/partials/result-syntax-output.txt", sdl);
}
@Test
public void verifyResultSyntaxMutationOnly() throws Exception {
final StringWriter target = new StringWriter();
tracker.addingBundle(U.mockProviderBundle(bundleContext, "SDL", 1, "a.sdl.txt", "mutation.only.txt"), null);
dsa.aggregate(target, "mutation.only");
// Parse the output with a real SDL parser
final String sdl = target.toString();
final TypeDefinitionRegistry reg = new SchemaParser().parse(sdl);
// And make sure it contains what we expect
assertTrue(reg.getDirectiveDefinition("fetcher").isPresent());
final Optional<TypeDefinition> mutation = reg.getType("Mutation");
assertTrue("Expecting Mutation", mutation.isPresent());
assertTrue(mutation.get().getChildren().toString().contains("theOnlyMutation"));
// A Query is required, even if not provided in any partial
final Optional<TypeDefinition> query = reg.getType("Query");
assertTrue("Expecting Query", query.isPresent());
assertOutput("/partials/result-syntax-output-mutation-only.txt", sdl);
}
@Test
public void requires() throws Exception {
final StringWriter target = new StringWriter();
tracker.addingBundle(U.mockProviderBundle(bundleContext, "SDL", 1, "a.sdl.txt", "b.sdl.txt", "c.sdl.txt"), null);
dsa.aggregate(target, "c.sdl");
final String sdl = target.toString();
// Verify that required partials are included
Stream.of(
"someMutation",
"typeFromB",
"typeFromA"
).forEach((s -> {
assertTrue("Expecting aggregate to contain " + s, sdl.contains(s));
}));
}
@Test
public void versionedPartials() throws IOException {
final StringWriter target = new StringWriter();
tracker.addingBundle(U.mockProviderBundle(bundleContext, "required.partials", 1, "required-1.0.0.txt"), null);
tracker.addingBundle(U.mockProviderBundle(bundleContext, "versioned.partials", 2, "versioned-1.0.0.txt"), null);
dsa.aggregate(target, "versioned-1.0.0");
assertOutput("/partials/versionedPartials-output.txt", target.toString());
}
@Test
public void versionedPartialsMissingCorrectVersion() throws IOException {
final StringWriter target = new StringWriter();
tracker.addingBundle(U.mockProviderBundle(bundleContext, "required.partials", 1, "required-1.0.0.txt"), null);
tracker.addingBundle(U.mockProviderBundle(bundleContext, "versioned.partials", 2, "versioned-2.0.0.txt"), null);
final IOException iox = assertThrows(IOException.class, () -> dsa.aggregate(target, "versioned-2.0.0"));
assertContainsIgnoreCase("Missing providers", iox.getMessage());
assertContainsIgnoreCase("required-2.0.0", iox.getMessage());
}
@Test
public void cycleInRequirements() throws Exception {
final StringWriter target = new StringWriter();
tracker.addingBundle(U.mockProviderBundle(bundleContext, "SDL", 1, "circularA.txt", "circularB.txt"), null);
final RuntimeException rex = assertThrows(RuntimeException.class, () -> dsa.aggregate(target, "circularA"));
Stream.of(
"requirements cycle",
"circularA"
).forEach((s -> {
assertTrue(String.format("Expecting message to contain %s: %s", s, rex.getMessage()), rex.getMessage().contains(s));
}));
}
@Test
public void providersOrdering() throws Exception {
final StringWriter target = new StringWriter();
tracker.addingBundle(U.mockProviderBundle(bundleContext, "ordering", 1, "aprov.txt", "cprov.txt", "z_test.txt", "a_test.txt",
"zprov.txt",
"z_test.txt", "bprov.txt", "c_test.txt"), null);
dsa.aggregate(target, "aprov", "zprov", "/[a-z]_test/", "a_test", "cprov");
final String sdl = target.toString();
// The order of named partials is kept, regexp selected ones are ordered by name
// And A_test has already been used so it's not used again when called explicitly after regexp
final String expected = "End of Schema aggregated from {aprov,zprov,a_test,c_test,z_test,cprov} by DefaultSchemaAggregator";
assertTrue(String.format("Expecting schema to contain [%s]: %s", expected, sdl), sdl.contains(expected));
}
}