SLING-7768: Added /etc/map tests to the resource resolver and extracted some common test code into MockTestUtil class
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java
new file mode 100644
index 0000000..b75b456
--- /dev/null
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/EtcMappingResourceResolverTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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.resourceresolver.impl;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.path.Path;
+import org.apache.sling.resourceresolver.impl.mapping.MapConfigurationProvider;
+import org.apache.sling.resourceresolver.impl.mapping.MapEntries;
+import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProviderConfiguration;
+import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProviderImpl;
+import org.apache.sling.resourceresolver.impl.providers.ResourceProviderHandler;
+import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorage;
+import org.apache.sling.resourceresolver.impl.providers.ResourceProviderTracker;
+import org.apache.sling.serviceusermapping.ServiceUserMapper;
+import org.apache.sling.spi.resource.provider.ResourceProvider;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.event.EventAdmin;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+import static org.apache.sling.resourceresolver.impl.MockedResourceResolverImplTest.createRPHandler;
+import static org.apache.sling.resourceresolver.impl.ResourceResolverImpl.PROP_REDIRECT_INTERNAL;
+import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.ExpectedEtcMapping;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.buildResource;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.callInaccessibleMethod;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.checkInternalResource;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.checkRedirectResource;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.createRequestFromUrl;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.setInaccessibleField;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+/**
+ * These are the same tests as in the EtcMappingMapEntriesTest but in this
+ * class we are actually mocking the Resource Resolver Factory and its classes
+ * and we test the mapping and resource resolution through the resource resolver
+ * rather the MapEntries.
+ */
+public class EtcMappingResourceResolverTest {
+
+    static final String PROP_REG_EXP = "sling:match";
+
+    @Mock
+    ResourceResolverFactory resourceResolverFactory;
+
+    @Mock
+    BundleContext bundleContext;
+
+    @Mock
+    Bundle bundle;
+
+    @Mock
+    EventAdmin eventAdmin;
+
+    @Mock
+    ResourceResolver resourceResolver;
+
+    @Mock
+    ResourceProvider<?> resourceProvider;
+
+    @Mock
+    StringInterpolationProviderConfiguration stringInterpolationProviderConfiguration;
+
+    StringInterpolationProviderImpl stringInterpolationProvider = new StringInterpolationProviderImpl();
+    MapEntries mapEntries;
+
+    File vanityBloomFilterFile;
+
+    CommonResourceResolverFactoryImpl commonFactory;
+
+    Resource etc;
+    Resource map;
+    Resource http;
+
+    Map<String, Map<String, String>> aliasMap;
+
+    @SuppressWarnings({"unchecked"})
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        List<MapConfigurationProvider.VanityPathConfig> configs = getVanityPathConfigs();
+        vanityBloomFilterFile = new File("target/test-classes/resourcesvanityBloomFilter.txt");
+        List<ResourceProviderHandler> handlers = asList(createRPHandler(resourceProvider, "rp1", 0, "/"));
+        ResourceProviderTracker resourceProviderTracker = mock(ResourceProviderTracker.class);
+        ResourceProviderStorage storage = new ResourceProviderStorage(handlers);
+        when(resourceProviderTracker.getResourceProviderStorage()).thenReturn(storage);
+        ResourceResolverFactoryActivator activator = new ResourceResolverFactoryActivator();
+        // These fields on the Activator a package private so we need reflection to access them
+        setInaccessibleField("resourceProviderTracker", activator, resourceProviderTracker);
+        setInaccessibleField("resourceAccessSecurityTracker", activator, new ResourceAccessSecurityTracker());
+        setInaccessibleField("bundleContext", activator, bundleContext);
+        setInaccessibleField("stringInterpolationProvider", activator, stringInterpolationProvider);
+        setInaccessibleField("mapRoot", activator, "/etc/map");
+        setInaccessibleField("mapRootPrefix", activator, "/etc/map");
+        setInaccessibleField("observationPaths", activator, new Path[] {new Path("/")});
+        ServiceUserMapper serviceUserMapper = mock(ServiceUserMapper.class);
+        setInaccessibleField("serviceUserMapper", activator, serviceUserMapper);
+        commonFactory = spy(new CommonResourceResolverFactoryImpl(activator));
+        when(bundleContext.getBundle()).thenReturn(bundle);
+        when(bundleContext.getDataFile("vanityBloomFilter.txt")).thenReturn(vanityBloomFilterFile);
+        when(serviceUserMapper.getServiceUserID(any(Bundle.class),anyString())).thenReturn("mapping");
+        // Activate method is package private so we use reflection to to call it
+        callInaccessibleMethod("activate", commonFactory, BundleContext.class, bundleContext);
+        final Bundle usingBundle = mock(Bundle.class);
+        resourceResolverFactory = new ResourceResolverFactoryImpl(commonFactory, usingBundle, null);
+        resourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(null);
+
+        etc = buildResource("/etc", null, resourceResolver, resourceProvider);
+        map = buildResource("/etc/map", etc, resourceResolver, resourceProvider);
+        http = buildResource("/etc/map/http", map, resourceResolver, resourceProvider);
+    }
+
+    List<MapConfigurationProvider.VanityPathConfig> getVanityPathConfigs() {
+        return new ArrayList<>();
+    }
+
+    void refreshMapEntries(String path, boolean isExternal) {
+        ((MapEntries) commonFactory.getMapEntries()).onChange(
+            asList(
+                new ResourceChange(ResourceChange.ChangeType.ADDED, path, isExternal)
+            )
+        );
+    }
+
+    @Test
+    public void root_node_to_content_mapping() throws Exception {
+        buildResource(http.getPath() + "/localhost.8080", http, resourceResolver, resourceProvider, PROP_REDIRECT_EXTERNAL, "/content/simple-node");
+        // This updates the map entries so that the newly added resources are added.
+        // ATTENTION: only call this after all etc-mapping resources are defined as this lock their Resource Meta Data and prevents a re-update
+        refreshMapEntries("/etc/map", true);
+
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/localhost.8080/", "/content/simple-node/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for root node to content", commonFactory.getMapEntries().getResolveMaps());
+
+        HttpServletRequest request = createRequestFromUrl("http://localhost:8080/");
+        Resource resolvedResource = resourceResolver.resolve(request, "/");
+        checkRedirectResource(resolvedResource, "/content/simple-node/", 302);
+    }
+
+    @Test
+    public void match_to_content_mapping() throws Exception {
+        buildResource("test-node", http, resourceResolver, resourceProvider,
+            PROP_REG_EXP, "localhost.8080/",
+            PROP_REDIRECT_EXTERNAL, "/content/simple-match/"
+        );
+        refreshMapEntries("/etc/map", true);
+
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/localhost.8080/", "/content/simple-match/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for match to content", commonFactory.getMapEntries().getResolveMaps());
+
+        HttpServletRequest request = createRequestFromUrl("http://localhost:8080/");
+        Resource resolvedResource = resourceResolver.resolve(request, "/");
+        checkRedirectResource(resolvedResource, "/content/simple-match/", 302);
+    }
+
+    // The following tests are based on the example from the https://sling.apache.org/documentation/the-sling-engine/mappings-for-resource-resolution.html page
+
+    @Test
+    public void internal_to_external_node_mapping() throws Exception {
+        buildResource("example.com.80", http, resourceResolver, resourceProvider, PROP_REDIRECT_EXTERNAL, "http://www.example.com/");
+        refreshMapEntries("/etc/map", true);
+
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/example.com.80/", "http://www.example.com/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for internal to external based on node", commonFactory.getMapEntries().getResolveMaps());
+
+        HttpServletRequest request = createRequestFromUrl("http://example.com/");
+        Resource resolvedResource = resourceResolver.resolve(request, "/");
+        checkRedirectResource(resolvedResource, "http://www.example.com/", 302);
+    }
+
+    @Test
+    public void internal_root_to_content_node_mapping() throws Exception {
+        buildResource("/example", null, resourceResolver, resourceProvider);
+
+        buildResource("www.example.com.80", http, resourceResolver, resourceProvider, PROP_REDIRECT_INTERNAL, "/example");
+        refreshMapEntries("/etc/map", true);
+
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping().addEtcMapEntry("^http/www.example.com.80/", true, "/example/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for internal root to content", commonFactory.getMapEntries().getResolveMaps());
+
+        HttpServletRequest request = createRequestFromUrl("http://www.example.com:80/");
+        Resource resolvedResource = resourceResolver.resolve(request, "/");
+        checkInternalResource(resolvedResource, "/example");
+    }
+
+    @Test
+    public void host_redirect_match_mapping() throws Exception {
+        buildResource("any_example.com.80", http, resourceResolver, resourceProvider,
+            PROP_REG_EXP, ".+\\.example\\.com\\.80",
+            PROP_REDIRECT_EXTERNAL, "http://www.example.com/"
+        );
+        refreshMapEntries("/etc/map", true);
+
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping().addEtcMapEntry("^http/.+\\.example\\.com\\.80", false, "http://www.example.com/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for host redirect match mapping", commonFactory.getMapEntries().getResolveMaps());
+
+        HttpServletRequest request = createRequestFromUrl("http://www.example.com");
+        Resource resolvedResource = resourceResolver.resolve(request, "/");
+        checkRedirectResource(resolvedResource, "http://www.example.com//", 302);
+    }
+
+    @Test
+    public void nested_internal_mixed_mapping() throws Exception {
+        Resource localhost = buildResource("localhost_any", http, resourceResolver, resourceProvider,
+            PROP_REG_EXP, "localhost\\.\\d*",
+            PROP_REDIRECT_INTERNAL, "/content"
+        );
+        buildResource("cgi-bin", localhost, resourceResolver, resourceProvider,PROP_REDIRECT_INTERNAL, "/scripts");
+        buildResource("gateway", localhost, resourceResolver, resourceProvider,PROP_REDIRECT_INTERNAL, "http://gbiv.com");
+        buildResource("(stories)", localhost, resourceResolver, resourceProvider,PROP_REDIRECT_INTERNAL, "/anecdotes/$1");
+
+        refreshMapEntries("/etc/map", true);
+
+        ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping()
+            .addEtcMapEntry("^http/localhost\\.\\d*", true, "/content")
+            .addEtcMapEntry("^http/localhost\\.\\d*/cgi-bin/", true, "/scripts/")
+            .addEtcMapEntry("^http/localhost\\.\\d*/gateway/", true, "http://gbiv.com/")
+            .addEtcMapEntry("^http/localhost\\.\\d*/(stories)/", true, "/anecdotes/$1/");
+        expectedEtcMapping.assertEtcMap("Etc Mapping for nested internal mixed mapping", commonFactory.getMapEntries().getResolveMaps());
+
+        buildResource("/content", null, resourceResolver, resourceProvider);
+        Resource scripts = buildResource("/scripts", null, resourceResolver, resourceProvider);
+        Resource scriptsChild = buildResource("/scripts/child", scripts, resourceResolver, resourceProvider);
+        Resource anecdotes = buildResource("/anecdotes", null, resourceResolver, resourceProvider);
+        Resource stories = buildResource("/anecdotes/stories", anecdotes, resourceResolver, resourceProvider);
+
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getScheme()).thenReturn("http");
+        when(request.getServerName()).thenReturn("localhost");
+        when(request.getServerPort()).thenReturn(1234);
+        Resource resolvedResource = resourceResolver.resolve(request, "/");
+        checkInternalResource(resolvedResource, "/content");
+
+        resolvedResource = resourceResolver.resolve(request, "/cgi-bin/");
+        checkInternalResource(resolvedResource, "/scripts");
+        resolvedResource = resourceResolver.resolve(request, "/cgi-bin/child/");
+        checkInternalResource(resolvedResource, "/scripts/child");
+//AS TODO: Does not redirect -> investigate later
+//        resolvedResource = resourceResolver.resolve(request, "/gateway/");
+//        checkRedirectResource(resolvedResource, "http://gbiv.com/", 302);
+        resolvedResource = resourceResolver.resolve(request, "/stories/");
+        checkInternalResource(resolvedResource, "/anecdotes/stories");
+    }
+}
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/SimpleValueMapImpl.java b/src/test/java/org/apache/sling/resourceresolver/impl/SimpleValueMapImpl.java
index f1ed9c1..be498aa 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/SimpleValueMapImpl.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/SimpleValueMapImpl.java
@@ -84,7 +84,12 @@
     public <T> T get(String name, Class<T> type) {
         Object o = delegate.get(name);
         if ( type.equals(String[].class) && ! ( o instanceof String[])) {
-            o = new String[] { String.valueOf(o) };
+            //AS This works fine unless o is a 'null'. It should return an empty string instead
+            if(o == null) {
+                o = new String[] {};
+            } else {
+                o = new String[]{String.valueOf(o)};
+            }
         }
         return (T) o;
     }
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
index 0f462e0..fdfbfab 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sling.resourceresolver.impl.mapping;
 
-import junit.framework.TestCase;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.api.resource.ResourceResolver;
@@ -26,7 +25,6 @@
 import org.apache.sling.api.wrappers.ValueMapDecorator;
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
@@ -38,7 +36,6 @@
 import java.io.File;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -49,8 +46,6 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.Semaphore;
 
-import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyMap;
@@ -222,78 +217,6 @@
         }
     }
 
-    static class ExpectedEtcMapping {
-        List<ExpectedEtcMapEntry> expectedEtcMapEntries = new ArrayList<>();
-
-        public ExpectedEtcMapping() {}
-
-        public ExpectedEtcMapping(String...expectedMapping) {
-            if(expectedMapping.length % 2 != 0) {
-                throw new IllegalArgumentException("Expect an even number of strings with pattern / redirect");
-            }
-            int size = expectedMapping.length / 2;
-            for(int i = 0; i < size; i++ ) {
-                expectedEtcMapEntries.add(new ExpectedEtcMapEntry(expectedMapping[2 * i], expectedMapping[2 * i + 1]));
-            }
-        }
-
-        public ExpectedEtcMapping addEtcMapEntry(String pattern, String redirect) {
-            addEtcMapEntry(pattern, false, redirect);
-            return this;
-        }
-        public ExpectedEtcMapping addEtcMapEntry(String pattern, boolean internal, String redirect) {
-            expectedEtcMapEntries.add(new ExpectedEtcMapEntry(pattern, internal, redirect));
-            return this;
-        }
-
-        public void assertEtcMap(String title, List<MapEntry> mapEntries) {
-            assertEquals("Wrong Number of Mappings for: " + title, expectedEtcMapEntries.size(), mapEntries.size());
-            ArrayList<MapEntry> actual = new ArrayList<>(mapEntries);
-            ArrayList<ExpectedEtcMapEntry> expected = new ArrayList<>(expectedEtcMapEntries);
-            for(MapEntry actualMapEntry: actual) {
-                ExpectedEtcMapEntry expectedFound = null;
-                for(ExpectedEtcMapEntry expectedEtcMapEntry: expected) {
-                    if(expectedEtcMapEntry.pattern.equals(actualMapEntry.getPattern())) {
-                        expectedFound = expectedEtcMapEntry;
-                        break;
-                    }
-                }
-                if(expectedFound == null) {
-                    TestCase.fail("This pattern (" + actualMapEntry.getPattern() + ") is not expected for: " + title);
-                }
-                expectedFound.assertEtcMap(title, actualMapEntry);
-                expected.remove(expectedFound);
-            }
-            for(ExpectedEtcMapEntry expectedEtcMapEntry: expected) {
-                TestCase.fail("Expected Map Entry (" + expectedEtcMapEntry.pattern + ") not provided for: " + title);
-            }
-        }
-    }
-
-    static class ExpectedEtcMapEntry {
-        private String pattern;
-        private boolean internal;
-        private String redirect;
-
-        public ExpectedEtcMapEntry(String pattern, String redirect) {
-            this(pattern, false, redirect);
-        }
-
-        public ExpectedEtcMapEntry(String pattern, boolean internal, String redirect) {
-            this.pattern = pattern;
-            this.internal = internal;
-            this.redirect = redirect;
-        }
-
-        public void assertEtcMap(String title, MapEntry mapEntry) {
-            assertEquals("Wrong Pattern for " + title, pattern, mapEntry.getPattern());
-            List<String> givenRedirects = new ArrayList<>(Arrays.asList(mapEntry.getRedirect()));
-            assertEquals("Wrong Number of Redirects for: " + title, 1, givenRedirects.size());
-            assertEquals("Wrong Redirect for: " + title, this.redirect, givenRedirects.get(0));
-            assertEquals("Wrong Redirect Type (ext/int) for: " + title, this.internal, mapEntry.isInternal());
-        }
-    }
-
     /**
      * Iterator to piggyback the list of Resources onto a Resource Mock
      * so that we can add children to them and create the iterators after
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java
index 5526ea0..a98ca6e 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java
@@ -17,7 +17,6 @@
 package org.apache.sling.resourceresolver.impl.mapping;
 
 import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.path.Path;
 import org.apache.sling.resourceresolver.impl.CommonResourceResolverFactoryImpl;
@@ -39,23 +38,23 @@
 import java.lang.reflect.Method;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 
 import static java.util.Arrays.asList;
 import static org.apache.sling.resourceresolver.impl.MockedResourceResolverImplTest.createRPHandler;
 import static org.apache.sling.resourceresolver.impl.ResourceResolverImpl.PROP_REDIRECT_INTERNAL;
 import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.ExpectedEtcMapping;
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyMap;
-import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+/**
+ * These tests are for the /etc/map setup of the Map Entries when
+ * an /etc/map is present.
+ */
 public class EtcMappingMapEntriesTest extends AbstractMappingMapEntriesTest {
 
     @Test
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java
index f1635a0..d500cdc 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java
@@ -19,13 +19,8 @@
 import org.apache.sling.api.resource.Resource;
 import org.junit.Test;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import static junit.framework.TestCase.fail;
 import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL;
-import static org.junit.Assert.assertEquals;
+import static org.apache.sling.resourceresolver.util.MockTestUtil.ExpectedEtcMapping;
 
 /**
  * These are tests that are testing the Sling Interpolation Feature (SLING-7768)
diff --git a/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java b/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
index 7db3831..4fb1e58 100644
--- a/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
+++ b/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java
@@ -16,13 +16,16 @@
  */
 package org.apache.sling.resourceresolver.util;
 
+import junit.framework.TestCase;
+import org.apache.sling.api.resource.NonExistingResource;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.api.wrappers.ValueMapDecorator;
 import org.apache.sling.resourceresolver.impl.SimpleValueMapImpl;
-import org.apache.sling.resourceresolver.impl.mapping.AbstractMappingMapEntriesTest;
+import org.apache.sling.resourceresolver.impl.helper.RedirectResource;
+import org.apache.sling.resourceresolver.impl.mapping.MapEntry;
 import org.apache.sling.spi.resource.provider.ResolveContext;
 import org.apache.sling.spi.resource.provider.ResourceContext;
 import org.apache.sling.spi.resource.provider.ResourceProvider;
@@ -30,39 +33,109 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 
 import static java.util.Arrays.asList;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
 
 public class MockTestUtil {
 
+    static final String PROP_SLING_TARGET = "sling:target";
+    static final String PROP_SLING_STATUS = "sling:status";
+
+    public static void checkRedirectResource(Resource redirect, String target, int status) {
+        assertThat("Not a Redirect Resource", redirect, instanceOf(RedirectResource.class));
+        RedirectResource redirectResource = (RedirectResource) redirect;
+        ValueMap values = redirectResource.adaptTo(ValueMap.class);
+        assertEquals("Redirect Target is wrong", target, values.get(PROP_SLING_TARGET, String.class));
+        assertEquals("Redirect Status is wrong", new Integer(status), values.get(PROP_SLING_STATUS, Integer.class));
+    }
+
+    public static void checkNonExistingResource(Resource redirect, String path) {
+        assertThat("Not a Non Existing Resource", redirect, instanceOf(NonExistingResource.class));
+        NonExistingResource nonExistingResource = (NonExistingResource) redirect;
+        if(path != null) {
+            assertEquals("Wrong Path for Non Existing Resource", path, nonExistingResource.getPath());
+        }
+    }
+
+    public static void checkInternalResource(Resource internal, String path) {
+//        assertThat("Not a Non Existing Resource", redirect, instanceOf(NonExistingResource.class));
+//        NonExistingResource nonExistingResource = (NonExistingResource) redirect;
+//        if(path != null) {
+        assertEquals("Wrong Path for Resource", path, internal.getPath());
+    }
+
     /**
      * Extract the name from a resource path
+     *
      * @param fullPath Full / Aboslute path to the resource
      * @return Name of the resource
      */
     public static String getResourceName(String fullPath) {
         int n = fullPath.lastIndexOf("/");
-        return fullPath.substring(n+1);
+        return fullPath.substring(n + 1);
+    }
+
+    /**
+     * Creates a Mock Http Servlet Request
+     * @param url Absolute URL to be used to get the method, host and port
+     * @return Http Servlet Request if the url is valid otherwise null
+     */
+    public static HttpServletRequest createRequestFromUrl(String url) {
+        int index = url.indexOf("://");
+        if(index > 0) {
+            String method = url.substring(0, index);
+            int port = 80;
+            int index2 = url.indexOf(":", index + 3);
+            int index3 = url.indexOf("/", index2 > index ? index2 : index + 3);
+            String host = "";
+            if (index2 > 0) {
+                port = new Integer(url.substring(index2 + 1, index3));
+                host = url.substring(index + 3, index2);
+            } else {
+                if(index3 > 0) {
+                    host = url.substring(index + 3, index3);
+                } else {
+                    host = url.substring(index + 3);
+                }
+            }
+            HttpServletRequest request = mock(HttpServletRequest.class);
+            when(request.getScheme()).thenReturn(method);
+            when(request.getServerName()).thenReturn(host);
+            when(request.getServerPort()).thenReturn(port);
+            return request;
+        }
+        return null;
     }
 
     /**
      * Build a resource with path, parent, provider and resource resolver.
-     * @param fullPath Full Path of the Resource
-     * @param parent Parent of this resource but it can be null
+     *
+     * @param fullPath         Full Path of the Resource
+     * @param parent           Parent of this resource but it can be null
      * @param resourceResolver Resource Resolver of this resource
-     * @param provider Resource Provider Instance
-     * @param properties Key / Value pair for resource properties (the number of strings must be even)
+     * @param provider         Resource Provider Instance
+     * @param properties       Key / Value pair for resource properties (the number of strings must be even)
      * @return
      */
     @SuppressWarnings("unchecked")
-    private Resource buildResource(String fullPath, Resource parent, ResourceResolver resourceResolver, ResourceProvider<?> provider, String ... properties) {
-        if(properties != null && properties.length % 2 != 0) { throw new IllegalArgumentException("List of Resource Properties must be an even number: " + asList(properties)); }
+    public static Resource buildResource(String fullPath, Resource parent, ResourceResolver resourceResolver, ResourceProvider<?> provider, String... properties) {
+        if (properties != null && properties.length % 2 != 0) {
+            throw new IllegalArgumentException("List of Resource Properties must be an even number: " + asList(properties));
+        }
         Resource resource = mock(Resource.class, withSettings().name(getResourceName(fullPath)).extraInterfaces(ResourceChildrenAccessor.class));
         when(resource.getName()).thenReturn(getResourceName(fullPath));
         when(resource.getPath()).thenReturn(fullPath);
@@ -70,7 +143,7 @@
         when(resource.getResourceMetadata()).thenReturn(resourceMetadata);
         when(resource.getResourceResolver()).thenReturn(resourceResolver);
 
-        if(parent != null) {
+        if (parent != null) {
             List<Resource> childList = ((ResourceChildrenAccessor) parent).getChildrenList();
             childList.add(resource);
         }
@@ -87,7 +160,7 @@
         });
 
         // register the resource with the provider
-        if ( provider != null ) {
+        if (provider != null) {
             when(provider.listChildren(Mockito.any(ResolveContext.class), Mockito.eq(resource))).thenAnswer(new Answer<Iterator<Resource>>() {
                 @Override
                 public Iterator<Resource> answer(InvocationOnMock invocation) throws Throwable {
@@ -96,11 +169,11 @@
             });
             when(provider.getResource(Mockito.any(ResolveContext.class), Mockito.eq(fullPath), Mockito.any(ResourceContext.class), Mockito.any(Resource.class))).thenReturn(resource);
         }
-        if ( properties != null ) {
+        if (properties != null) {
             ValueMap vm = new SimpleValueMapImpl();
-            for ( int i=0; i < properties.length; i+=2) {
-                resourceMetadata.put(properties[i], properties[i+1]);
-                vm.put(properties[i], properties[i+1]);
+            for (int i = 0; i < properties.length; i += 2) {
+                resourceMetadata.put(properties[i], properties[i + 1]);
+                vm.put(properties[i], properties[i + 1]);
             }
             when(resource.getValueMap()).thenReturn(vm);
             when(resource.adaptTo(Mockito.eq(ValueMap.class))).thenReturn(vm);
@@ -112,6 +185,32 @@
         return resource;
     }
 
+    public static Object callInaccessibleMethod(String methodName, Object target, Class paramsType, Object param) throws NoSuchMethodException {
+        try {
+            Method method = target.getClass().getDeclaredMethod(methodName, paramsType);
+            method.setAccessible(true);
+            return method.invoke(target, param);
+        } catch(NoSuchMethodException e) {
+            throw new UnsupportedOperationException("Failed to find method: " + methodName, e);
+        } catch (IllegalAccessException e) {
+            throw new UnsupportedOperationException("Failed to access method: " + methodName, e);
+        } catch (InvocationTargetException e) {
+            throw new UnsupportedOperationException("Failed to invoke method: " + methodName, e);
+        }
+    }
+
+    public static void setInaccessibleField(String fieldName, Object target, Object fieldValue) throws NoSuchMethodException {
+        try {
+            Field field = target.getClass().getDeclaredField(fieldName);
+            field.setAccessible(true);
+            field.set(target, fieldValue);
+        } catch (IllegalAccessException e) {
+            throw new UnsupportedOperationException("Failed to access field: " + fieldName, e);
+        } catch (NoSuchFieldException e) {
+            e.printStackTrace();
+        }
+    }
+
     /**
      * Iterator to piggyback the list of Resources onto a Resource Mock
      * so that we can add children to them and create the iterators after
@@ -121,4 +220,75 @@
         public List<Resource> getChildrenList();
     }
 
+    public static class ExpectedEtcMapping {
+        List<ExpectedEtcMapEntry> expectedEtcMapEntries = new ArrayList<>();
+
+        public ExpectedEtcMapping() {}
+
+        public ExpectedEtcMapping(String...expectedMapping) {
+            if(expectedMapping.length % 2 != 0) {
+                throw new IllegalArgumentException("Expect an even number of strings with pattern / redirect");
+            }
+            int size = expectedMapping.length / 2;
+            for(int i = 0; i < size; i++ ) {
+                expectedEtcMapEntries.add(new ExpectedEtcMapEntry(expectedMapping[2 * i], expectedMapping[2 * i + 1]));
+            }
+        }
+
+        public ExpectedEtcMapping addEtcMapEntry(String pattern, String redirect) {
+            addEtcMapEntry(pattern, false, redirect);
+            return this;
+        }
+        public ExpectedEtcMapping addEtcMapEntry(String pattern, boolean internal, String redirect) {
+            expectedEtcMapEntries.add(new ExpectedEtcMapEntry(pattern, internal, redirect));
+            return this;
+        }
+
+        public void assertEtcMap(String title, List<MapEntry> mapEntries) {
+            assertEquals("Wrong Number of Mappings for: " + title, expectedEtcMapEntries.size(), mapEntries.size());
+            ArrayList<MapEntry> actual = new ArrayList<>(mapEntries);
+            ArrayList<ExpectedEtcMapEntry> expected = new ArrayList<>(expectedEtcMapEntries);
+            for(MapEntry actualMapEntry: actual) {
+                ExpectedEtcMapEntry expectedFound = null;
+                for(ExpectedEtcMapEntry expectedEtcMapEntry: expected) {
+                    if(expectedEtcMapEntry.pattern.equals(actualMapEntry.getPattern())) {
+                        expectedFound = expectedEtcMapEntry;
+                        break;
+                    }
+                }
+                if(expectedFound == null) {
+                    TestCase.fail("This pattern (" + actualMapEntry.getPattern() + ") is not expected for: " + title);
+                }
+                expectedFound.assertEtcMap(title, actualMapEntry);
+                expected.remove(expectedFound);
+            }
+            for(ExpectedEtcMapEntry expectedEtcMapEntry: expected) {
+                TestCase.fail("Expected Map Entry (" + expectedEtcMapEntry.pattern + ") not provided for: " + title);
+            }
+        }
+    }
+
+    public static class ExpectedEtcMapEntry {
+        private String pattern;
+        private boolean internal;
+        private String redirect;
+
+        public ExpectedEtcMapEntry(String pattern, String redirect) {
+            this(pattern, false, redirect);
+        }
+
+        public ExpectedEtcMapEntry(String pattern, boolean internal, String redirect) {
+            this.pattern = pattern;
+            this.internal = internal;
+            this.redirect = redirect;
+        }
+
+        public void assertEtcMap(String title, MapEntry mapEntry) {
+            assertEquals("Wrong Pattern for " + title, pattern, mapEntry.getPattern());
+            List<String> givenRedirects = new ArrayList<>(Arrays.asList(mapEntry.getRedirect()));
+            assertEquals("Wrong Number of Redirects for: " + title, 1, givenRedirects.size());
+            assertEquals("Wrong Redirect for: " + title, this.redirect, givenRedirects.get(0));
+            assertEquals("Wrong Redirect Type (ext/int) for: " + title, this.internal, mapEntry.isInternal());
+        }
+    }
 }