/**
* Copyright 2013 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 io.neba.core.resourcemodels.registration;
import io.neba.core.util.OsgiBeanSource;
import org.apache.commons.io.IOUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.osgi.framework.Bundle;
import org.osgi.framework.Version;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.util.*;
import static java.lang.System.arraycopy;
import static org.apache.commons.io.IOUtils.toByteArray;
import static org.apache.commons.lang.StringUtils.substringAfterLast;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
/**
* @author Olaf Otto
*/
@RunWith(MockitoJUnitRunner.class)
public class ModelRegistryConsolePluginTest {
/**
* @author Olaf Otto
*/
private static class Model {}
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
@Mock
private ModelRegistry modelRegistry;
@Mock
private ResourceResolverFactory factory;
@Mock
private ResourceResolver resolver;
@Mock
private Resource iconResource;
@Mock
private Resource pathResource;
@Mock
private ServletOutputStream outputStream;
@Mock
private Bundle bundle;
@Mock
private Version version;
private URL resourceUrl;
private Writer internalWriter;
private String renderedResponse;
private Map<String, Collection<OsgiBeanSource<?>>> typeMappings;
private Collection<OsgiBeanSource<?>> beanSources;
@InjectMocks
private ModelRegistryConsolePlugin testee;
@Before
@SuppressWarnings("unchecked")
public void setUp() throws Exception {
this.internalWriter = new StringWriter();
Writer writer = new PrintWriter(this.internalWriter);
this.typeMappings = new HashMap<>();
this.beanSources = new ArrayList<>();
doReturn(writer).when(this.response).getWriter();
doReturn(this.typeMappings).when(this.modelRegistry).getTypeMappings();
doReturn(this.beanSources).when(this.modelRegistry).getBeanSources();
doReturn(this.resolver).when(this.factory).getAdministrativeResourceResolver(any());
doReturn(new String[]{""}).when(this.resolver).getSearchPath();
doReturn("").when(this.request).getContextPath();
doReturn("/system/console").when(this.request).getServletPath();
doReturn(this.outputStream).when(this.response).getOutputStream();
doReturn("JUnit test bundle").when(this.bundle).getSymbolicName();
doReturn(this.version).when(this.bundle).getVersion();
doReturn("1.0.0").when(this.version).toString();
}
@Test
public void testRenderingOfRegisteredModelsTable() throws Exception {
withRegisteredModel("cq:Page", Model.class, 123L, "beanName");
renderContent();
assertResponseContainsTableHead();
assertResponseContainsNumberOfModelsText(1);
assertResponseContains("<span class=\"unresolved\">cq:Page</span>", Model.class, 123L, "beanName");
}
@Test
public void testRenderingOfLinkToCrxDe() throws Exception {
withRegisteredModel("cq:Page", Model.class, 123L, "beanName");
withResolution("cq/Page", "/libs/foundation/components/primary/cq/Page");
renderContent();
assertResponseContains("<a href=\"/crx/de/#" +
"/libs/foundation/components/primary/cq/Page\" class=\"crxdelink\">" +
"<img class=\"componentIcon\" src=\"modelregistry/api/componenticon\"/>cq:Page</a>", Model.class, 123L, "beanName");
}
@Test
public void testRetrievalOfStaticJavascript() throws Exception {
getResource("script.js");
assertResourceContains("function toggleUnresolvedResourceTypes()");
}
@Test
public void testRenderingOfExistingComponentIcon() throws Exception {
withIconResource("/apps/project/components/myComponent/icon.png");
get("/system/console/modelregistry/api/componenticon/apps/project/components/myComponent");
verifyPluginResolvesResource("/apps/project/components/myComponent/icon.png");
verifyResponseHasContentType("image/png");
verifyIconResourceIsAdaptedToInputStream();
}
@Test
public void testRenderingOfDefaultComponentIcon() throws Exception {
get("/system/console/modelregistry/api/componenticon");
verifyResponseHasContentType("image/png");
verifyNoIconResourceIsResolved();
verifyDefaultComponentIconIsWritten();
}
@Test
public void testModelTypesApi() throws Exception {
withRegisteredModel("cq:Page", Model.class, 123L, "beanName");
get("/system/console/modelregistry/api/modeltypes");
verifyResponseHasContentType("application/json;charset=UTF-8");
assertResponseIs("[\"io.neba.core.resourcemodels.registration.ModelRegistryConsolePluginTest$Model\"]");
}
@Test
public void testListChildrenViaResourcesApi() throws Exception {
withPathResource("/junit/test");
withPathResourceChildren("/junit/test/1", "/junit/test/2");
withParameter("path", "/junit/test/");
get("/system/console/modelregistry/api/resources");
verifyResponseHasContentType("application/json;charset=UTF-8");
assertResponseIs("[\"/junit/test/1\",\"/junit/test/2\"]");
}
@Test
public void testListChildrenOfRootViaResourcesApi() throws Exception {
withPathResource("/");
withPathResourceChildren("/junit", "/test");
withParameter("path", "/");
get("/system/console/modelregistry/api/resources");
verifyResponseHasContentType("application/json;charset=UTF-8");
assertResponseIs("[\"/junit\",\"/test\"]");
}
@Test
public void testFilterChildrenViaResourcesApi() throws Exception {
withPathResource("/junit/test");
withPathResourceChildren("/junit/test/1", "/junit/test/2");
withParameter("path", "/junit/test/1");
get("/system/console/modelregistry/api/resources");
verifyResponseHasContentType("application/json;charset=UTF-8");
assertResponseIs("[\"/junit/test/1\"]");
}
@Test
public void testFilterModelTypesByPackageName() throws Exception {
withRegisteredModel("cq:Page", Model.class, 123L, "beanName");
withParameter("modelTypeName", "io.neba");
get("/system/console/modelregistry/api/filter");
verifyResponseHasContentType("application/json;charset=UTF-8");
assertResponseIs("[\"io.neba.core.resourcemodels.registration.ModelRegistryConsolePluginTest$Model\"]");
}
@Test
public void testFilterModelTypesByTypeName() throws Exception {
withRegisteredModel("cq:Page", Model.class, 123L, "beanName");
withParameter("modelTypeName", "io.neba.core.resourcemodels.registration.ModelRegistryConsolePluginTest$Model");
get("/system/console/modelregistry/api/filter");
verifyResponseHasContentType("application/json;charset=UTF-8");
assertResponseIs("[\"io.neba.core.resourcemodels.registration.ModelRegistryConsolePluginTest$Model\"]");
}
@Test
public void testFilterModelTypesByResource() throws Exception {
withRegisteredModel("cq:Page", Model.class, 123L, "beanName");
withPathResource("/junit/test", "cq:Page");
withPathResourceLookedUp();
withParameter("path", "/junit/test");
get("/system/console/modelregistry/api/filter");
verifyResponseHasContentType("application/json;charset=UTF-8");
assertResponseIs("[\"io.neba.core.resourcemodels.registration.ModelRegistryConsolePluginTest$Model\"]");
}
@Test
public void testFilterModelTypesByResourceAndModelType() throws Exception {
withRegisteredModel("cq:Page", Model.class, 123L, "beanName");
withPathResource("/junit/test", "cq:Page");
withPathResourceLookedUp();
withParameter("path", "/junit/test");
withParameter("modelTypeName", "io.neba.core.resourcemodels.registration.ModelRegistryConsolePluginTest$Model");
get("/system/console/modelregistry/api/filter");
verifyResponseHasContentType("application/json;charset=UTF-8");
assertResponseIs("[\"io.neba.core.resourcemodels.registration.ModelRegistryConsolePluginTest$Model\"]");
}
private void withPathResourceLookedUp() {
Set<LookupResult> lookupResults = new HashSet<>();
for (OsgiBeanSource<?> source : this.beanSources) {
LookupResult result = mock(LookupResult.class);
doReturn(source).when(result).getSource();
doReturn(pathResource.getResourceType()).when(result).getResourceType();
lookupResults.add(result);
}
doReturn(lookupResults).when(this.modelRegistry).lookupAllModels(eq(this.pathResource));
}
private void withPathResourceChildren(String... paths) {
List<Resource> children = new ArrayList<>();
for (String path : paths) {
Resource child = mock(Resource.class);
doReturn(path).when(child).getPath();
doReturn(substringAfterLast(path, "/")).when(child).getName();
children.add(child);
}
doReturn(children.iterator()).when(this.pathResource).listChildren();
}
private void withPathResource(String path) {
withPathResource(path, null);
}
private void withPathResource(String path, String type) {
doReturn(path).when(this.pathResource).getPath();
doReturn(this.pathResource).when(this.resolver).getResource(eq(path));
doReturn(type).when(this.pathResource).getResourceType();
}
private void withParameter(String name, String value) {
doReturn(value).when(this.request).getParameter(eq(name));
}
private void withIconResource(String resourcePath) throws IOException {
doReturn(this.iconResource).when(this.resolver).getResource(eq(resourcePath));
InputStream in = mock(InputStream.class);
doReturn(in).when(this.iconResource).adaptTo(eq(InputStream.class));
doReturn(-1).when(in).read(any());
}
private void verifyPluginResolvesResource(String resourcePath) {
verify(this.resolver).getResource(resourcePath);
}
private void verifyNoIconResourceIsResolved() {
verify(this.resolver, never()).getResource(anyString());
}
private void verifyDefaultComponentIconIsWritten() throws IOException {
byte[] buffer = new byte[4096];
byte[] expected = toByteArray(getClass().getResourceAsStream("/META-INF/consoleplugin/modelregistry/static/noicon.png"));
arraycopy(expected, 0, buffer, 0, expected.length);
for (int i = expected.length; i < buffer.length; ++i) {
buffer[i] = 0;
}
verify(this.outputStream).write(buffer, 0, expected.length);
}
private void get(String requestUri) throws ServletException, IOException {
doReturn(requestUri).when(this.request).getRequestURI();
this.testee.doGet(this.request, this.response);
getResponseAsString();
}
private void assertResourceContains(String resourceFragment) throws IOException {
assertThat(this.resourceUrl).isNotNull();
assertThat(IOUtils.toString(this.resourceUrl.openStream())).contains(resourceFragment);
}
private void withResolution(String resourceTypePath, String resourcePath) {
Resource mock = mock(Resource.class);
doReturn(mock).when(this.resolver).getResource(eq(resourceTypePath));
doReturn(resourcePath).when(mock).getPath();
}
private void assertResponseContains(String typeName, Class<Model> modelType, long bundleId, String beanName) {
assertThat(this.renderedResponse).contains("<td>" + typeName + "</td><td>" + modelType.getName() +
"</td><td>" + beanName + "</td><td><a href=\"bundles/" + bundleId +
"\" title=\"JUnit test bundle 1.0.0\">" + bundleId + "</a></td>");
}
private void assertResponseContainsNumberOfModelsText(int numberOfTests) {
assertThat(this.renderedResponse).contains(numberOfTests + " Model(s) registered.");
}
private void assertResponseIs(String expected) {
assertThat(this.renderedResponse).isEqualTo(expected);
}
private void withRegisteredModel(String typeName, Class<Model> modelType, long bundleId, String beanName) {
List<OsgiBeanSource<?>> sources = new ArrayList<>();
OsgiBeanSource<?> source = mock(OsgiBeanSource.class);
doReturn(modelType).when(source).getBeanType();
doReturn(bundleId).when(source).getBundleId();
doReturn(beanName).when(source).getBeanName();
doReturn(this.bundle).when(source).getBundle();
sources.add(source);
this.beanSources.add(source);
this.typeMappings.put(typeName, sources);
}
private void assertResponseContainsTableHead() {
assertThat(this.renderedResponse).contains("<th>Type</th>");
assertThat(this.renderedResponse).contains("<th>Model type</th>");
assertThat(this.renderedResponse).contains("<th>Bean name</th>");
assertThat(this.renderedResponse).contains("<th>Source bundle</th>");
}
private void renderContent() throws ServletException, IOException {
this.testee.renderContent(this.request, this.response);
// Remove platform-dependent line endings.
getResponseAsString();
}
private void getResponseAsString() {
this.renderedResponse = this.internalWriter.toString().replaceAll("[\\n\\r]", "");
}
private void getResource(String resource) {
String resourcePath = "/" + ModelRegistryConsolePlugin.LABEL + "/static/" + resource;
this.resourceUrl = this.testee.getResource(resourcePath);
}
private void verifyIconResourceIsAdaptedToInputStream() {
verify(this.iconResource).adaptTo(eq(InputStream.class));
}
private void verifyResponseHasContentType(String type) {
verify(this.response).setContentType(type);
}
}