content: /*
   * Copyright (c) 2008. All rights reserved.
   */
  package ro.isdc.wro.manager;
  
  import static org.junit.Assert.assertEquals;
  import static org.mockito.Mockito.atLeastOnce;
  import static org.mockito.Mockito.verify;
  
  import java.io.BufferedInputStream;
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.Reader;
  import java.io.Writer;
  import java.net.URL;
  import java.util.Arrays;
  
  import javax.servlet.FilterConfig;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  
  import org.apache.commons.io.output.WriterOutputStream;
  import org.junit.After;
  import org.junit.AfterClass;
  import org.junit.Assert;
  import org.junit.Before;
  import org.junit.BeforeClass;
  import org.junit.Test;
  import org.mockito.Mock;
  import org.mockito.Mockito;
  import org.mockito.MockitoAnnotations;
  import org.mockito.invocation.InvocationOnMock;
  import org.mockito.stubbing.Answer;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import ro.isdc.wro.WroRuntimeException;
  import ro.isdc.wro.cache.CacheKey;
  import ro.isdc.wro.cache.CacheStrategy;
  import ro.isdc.wro.cache.CacheValue;
  import ro.isdc.wro.config.Context;
  import ro.isdc.wro.config.jmx.WroConfiguration;
  import ro.isdc.wro.http.support.DelegatingServletOutputStream;
  import ro.isdc.wro.http.support.HttpHeader;
  import ro.isdc.wro.manager.callback.LifecycleCallback;
  import ro.isdc.wro.manager.callback.PerformanceLoggerCallback;
  import ro.isdc.wro.manager.factory.BaseWroManagerFactory;
  import ro.isdc.wro.manager.factory.NoProcessorsWroManagerFactory;
  import ro.isdc.wro.manager.factory.WroManagerFactory;
  import ro.isdc.wro.manager.runnable.ReloadModelRunnable;
  import ro.isdc.wro.model.WroModel;
  import ro.isdc.wro.model.factory.WroModelFactory;
  import ro.isdc.wro.model.factory.XmlModelFactory;
  import ro.isdc.wro.model.group.DefaultGroupExtractor;
  import ro.isdc.wro.model.group.Group;
  import ro.isdc.wro.model.group.GroupExtractor;
  import ro.isdc.wro.model.group.processor.Injector;
  import ro.isdc.wro.model.group.processor.InjectorBuilder;
  import ro.isdc.wro.model.resource.Resource;
  import ro.isdc.wro.model.resource.ResourceType;
  import ro.isdc.wro.model.resource.processor.Destroyable;
  import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
  import ro.isdc.wro.model.resource.processor.factory.SimpleProcessorsFactory;
  import ro.isdc.wro.model.resource.processor.impl.PlaceholderProcessor;
  import ro.isdc.wro.model.resource.support.MutableResourceAuthorizationManager;
  import ro.isdc.wro.model.resource.support.hash.CRC32HashStrategy;
  import ro.isdc.wro.model.resource.support.hash.MD5HashStrategy;
  import ro.isdc.wro.util.AbstractDecorator;
  import ro.isdc.wro.util.ObjectFactory;
  import ro.isdc.wro.util.WroTestUtils;
  import ro.isdc.wro.util.WroUtil;
  import ro.isdc.wro.util.io.UnclosableBufferedInputStream;
  
  
  /**
   * TestWroManager.java.
   *
   * @author Alex Objelean
   * @created Created on Nov 3, 2008
   */
  public class TestWroManager {
    private static final Logger LOG = LoggerFactory.getLogger(TestWroManager.class);
    @Mock
    private MutableResourceAuthorizationManager mockAuthorizationManager;
    @Mock
    private CacheStrategy<CacheKey, CacheValue> mockCacheStrategy;
    @Mock
    private WroModelFactory mockModelFactory;
    /**
     * Used to test simple operations.
     */
    private WroManager victim;
    /**
     * Used to test more complex use-cases.
     */
    private WroManagerFactory managerFactory;
  
    @BeforeClass
    public static void onBeforeClass() {
      assertEquals(0, Context.countActive());
    }
  
    @AfterClass
    public static void onAfterClass() {
      assertEquals(0, Context.countActive());
    }
  
    @Before
    public void setUp() {
      MockitoAnnotations.initMocks(this);
      final Context context = Context.webContext(Mockito.mock(HttpServletRequest.class),
          Mockito.mock(HttpServletResponse.class, Mockito.RETURNS_DEEP_STUBS), Mockito.mock(FilterConfig.class));
      Context.set(context, newConfigWithUpdatePeriodValue(0));
  
      managerFactory = new BaseWroManagerFactory().setModelFactory(getValidModelFactory()).setResourceAuthorizationManager(
          mockAuthorizationManager);
  
      final Injector injector = new InjectorBuilder(managerFactory).build();
      victim = managerFactory.create();
      injector.inject(victim);
    }
  
    /**
     * A processor which which uses a {@link WroManager} during processor, in order to process a single group, whose
     * resource is the pre processed resource of this processor.
     */
    private static final class WroManagerProcessor
        implements ResourcePreProcessor {
  
      private BaseWroManagerFactory createManagerFactory(final Resource resource) {
        return new BaseWroManagerFactory() {
          @Override
          protected void onAfterInitializeManager(final WroManager manager) {
            manager.registerCallback(new ObjectFactory<LifecycleCallback>() {
              public LifecycleCallback create() {
                return new PerformanceLoggerCallback();
              }
            });
          };
  
          @Override
          protected WroModelFactory newModelFactory() {
            return WroTestUtils.simpleModelFactory(new WroModel().addGroup(new Group("group").addResource(resource)));
          }
        };
      }
  
      public void process(final Resource resource, final Reader reader, final Writer writer)
          throws IOException {
        LOG.debug("resource: {}", resource);
  
        final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
        final HttpServletResponse response = Context.get().getResponse();
  
        Mockito.when(response.getOutputStream()).thenReturn(
            new DelegatingServletOutputStream(new WriterOutputStream(writer)));
        Mockito.when(request.getRequestURI()).thenReturn("");
  
        final WroConfiguration config = new WroConfiguration();
        // we don't need caching here, otherwise we'll have clashing during unit tests.
        config.setDebug(true);
        Context.unset();
        Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)), config);
  
        // create a groupExtractor which always return the same group name.
        final String groupName = "group";
        final GroupExtractor groupExtractor = new DefaultGroupExtractor() {
          @Override
          public String getGroupName(final HttpServletRequest request) {
            return groupName;
          }
  
          @Override
          public ResourceType getResourceType(final HttpServletRequest request) {
            return resource.getType();
          }
        };
        // this manager will make sure that we always process a model holding one group which has only one resource.
        final WroManagerFactory managerFactory = createManagerFactory(resource).setGroupExtractor(groupExtractor);
        managerFactory.create().process();
      }
    }
  
    private class GenericTestBuilder {
      /**
       * Perform a processing on a group extracted from requestUri and compares with the expectedResourceUri content.
       *
       * @param requestUri
       *          contains the group name to process.
       * @param expectedResourceUri
       *          the uri of the resource which has the expected content.
       */
      public void processAndCompare(final String requestUri, final String expectedResourceUri)
          throws Exception {
        final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
        final HttpServletResponse response = Context.get().getResponse();
  
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        Mockito.when(response.getOutputStream()).thenReturn(new DelegatingServletOutputStream(out));
        Mockito.when(request.getRequestURI()).thenReturn(requestUri);
  
        final WroConfiguration config = new WroConfiguration();
        config.setIgnoreFailingProcessor(true);
        Context.unset();
        Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)), config);
  
        onBeforeProcess();
  
        managerFactory.create().process();
  
        // compare written bytes to output stream with the content from specified css.
        final InputStream expectedInputStream = new UnclosableBufferedInputStream(
            WroTestUtils.getInputStream(expectedResourceUri));
        final InputStream actualInputStream = new BufferedInputStream(new ByteArrayInputStream(out.toByteArray()));
        expectedInputStream.reset();
        WroTestUtils.compare(expectedInputStream, actualInputStream);
        expectedInputStream.close();
        actualInputStream.close();
      }
  
      /**
       * Allow to execute custom logic before the actual processing is done.
       */
      protected void onBeforeProcess() {
      }
    }
  
    /**
     * @return a {@link XmlModelFactory} pointing to a valid config resource.
     */
    private static XmlModelFactory getValidModelFactory() {
      return new XmlModelFactory() {
        @Override
        protected InputStream getModelResourceAsStream() {
          return TestWroManager.class.getResourceAsStream("wro.xml");
        }
      };
    }
  
    @Test(expected = NullPointerException.class)
    public void cannotRegisterNullCallback() {
      final WroManager manager = new BaseWroManagerFactory().create();
      manager.registerCallback(null);
    }
  
    /**
     * Ignored because it fails when running the test from command line.
     */
    @Test
    public void testFromFolder()
        throws Exception {
      final ResourcePreProcessor processor = new WroManagerProcessor();
      final URL url = getClass().getResource("wroManager");
  
      final File testFolder = new File(url.getFile(), "test");
      final File expectedFolder = new File(url.getFile(), "expected");
      WroTestUtils.compareFromDifferentFoldersByExtension(testFolder, expectedFolder, "js", processor);
    }
  
    /**
     * Initialize {@link WroConfiguration} object with cacheUpdatePeriod & modelUpdatePeriod equal with provided argument.
     */
    private WroConfiguration newConfigWithUpdatePeriodValue(final long periodValue) {
      final WroConfiguration config = new WroConfiguration();
      config.setCacheUpdatePeriod(periodValue);
      config.setModelUpdatePeriod(periodValue);
      return config;
    }
  
    @Test
    public void shouldClearAuthorizationManagerWhenCachePeriodChanged() {
      victim.onCachePeriodChanged(1);
      verify(mockAuthorizationManager, atLeastOnce()).clear();
    }
  
    @Test
    public void shouldClearAuthorizationManagerWhenModelPeriodChanged() {
      victim.onModelPeriodChanged(1);
      verify(mockAuthorizationManager, atLeastOnce()).clear();
    }
  
    @Test
    public void testNoProcessorWroManagerFactory()
        throws IOException {
      final WroManagerFactory factory = new NoProcessorsWroManagerFactory().setModelFactory(getValidModelFactory());
  
      final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
      final HttpServletResponse response = Context.get().getResponse();
  
      final ByteArrayOutputStream out = new ByteArrayOutputStream();
      Mockito.when(response.getOutputStream()).thenReturn(new DelegatingServletOutputStream(out));
      Mockito.when(request.getRequestURI()).thenReturn("/app/g1.css");
      Context.unset();
      Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)));
  
      factory.create().process();
  
      // compare written bytes to output stream with the content from specified css.
      WroTestUtils.compare(WroTestUtils.getInputStream("classpath:ro/isdc/wro/manager/noProcessorsResult.css"),
          new ByteArrayInputStream(out.toByteArray()));
    }
  
    @Test
    public void testDuplicatedResourcesShouldBeSkipped()
        throws Exception {
      new GenericTestBuilder().processAndCompare("/repeatedResources.js", "classpath:ro/isdc/wro/manager/repeated-out.js");
    }
  
    @Test
    public void testWildcardDuplicatedResourcesShouldBeSkiped()
        throws Exception {
      new GenericTestBuilder().processAndCompare("/wildcardRepeatedResources.js",
          "classpath:ro/isdc/wro/manager/wildcardRepeated-out.js");
    }
  
    @Test
    public void testMinimizeAttributeIsFalseOnResource()
        throws Exception {
      new GenericTestBuilder().processAndCompare("/resourceMinimizeFalse.js", "classpath:ro/isdc/wro/manager/sample.js");
    }
  
    @Test
    public void testMinimizeAttributeIsTrueOnResource()
        throws Exception {
      new GenericTestBuilder().processAndCompare("/resourceMinimizeTrue.js",
          "classpath:ro/isdc/wro/manager/sample.min.js");
    }
  
    @Test
    public void testWildcardGroupResources()
        throws Exception {
      new GenericTestBuilder().processAndCompare("/wildcardResources.js", "classpath:ro/isdc/wro/manager/wildcard-out.js");
    }
  
    /**
     * Test that when ignoreMissingResource is true and IOException is thrown by a processor, no exception is thrown.
     */
    @Test
    public void testCssWithInvalidImport()
        throws Exception {
      new GenericTestBuilder().processAndCompare("/invalidImport.css",
          "classpath:ro/isdc/wro/manager/invalidImport-out.css");
    }
  
    @Test(expected = WroRuntimeException.class)
    public void shouldNotIgnoreInvalidImportWhenImportedResourceIsMissing()
        throws Exception {
      genericIgnoreMissingResourceTest(false);
    }
  
    @Test
    public void shouldIgnoreInvalidImportWhenImportedResourceIsMissing()
        throws Exception {
      genericIgnoreMissingResourceTest(true);
    }
  
    private void genericIgnoreMissingResourceTest(final boolean ignoreFlag)
        throws Exception {
      new GenericTestBuilder() {
        @Override
        protected void onBeforeProcess() {
          final WroConfiguration config = Context.get().getConfig();
          config.setIgnoreFailingProcessor(ignoreFlag);
          config.setIgnoreMissingResources(ignoreFlag);
        };
      }.processAndCompare("/invalidImport.css", "classpath:ro/isdc/wro/manager/invalidImport-out.css");
    }
  
    @Test
    public void processValidModel()
        throws IOException {
      final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
      final HttpServletResponse response = Mockito.mock(HttpServletResponse.class, Mockito.RETURNS_DEEP_STUBS);
      Mockito.when(request.getRequestURI()).thenReturn("/app/g1.css");
  
      // Test also that ETag header value contains quotes
      Mockito.doAnswer(new Answer<Void>() {
        public Void answer(final InvocationOnMock invocation)
            throws Throwable {
          LOG.debug("Header: {}", Arrays.toString(invocation.getArguments()));
          final Object[] arguments = invocation.getArguments();
          if (HttpHeader.ETAG.toString().equals(arguments[0])) {
            final String etagHeaderValue = (String) arguments[1];
            Assert.assertTrue(etagHeaderValue.matches("\".*?\""));
          }
          return null;
        }
      }).when(response).setHeader(Mockito.anyString(), Mockito.anyString());
      Context.unset();
      Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)));
  
      managerFactory.create().process();
    }
  
    @Test
    public void testManagerWithSchedulerAndUpdatePeriodSet()
        throws Exception {
      final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
      Mockito.when(request.getRequestURI()).thenReturn("/app/g1.css");
      final Context context = Context.webContext(request,
          Mockito.mock(HttpServletResponse.class, Mockito.RETURNS_DEEP_STUBS), Mockito.mock(FilterConfig.class));
      final WroConfiguration config = new WroConfiguration();
      // make it run each 1 second
      config.setModelUpdatePeriod(1);
      config.setCacheUpdatePeriod(1);
      Context.unset();
      Context.set(context, config);
  
      managerFactory.create().process();
      // let scheduler run a while
      Thread.sleep(1100);
    }
  
    /**
     * Test how manager behaves when the update period value is greater than zero and the scheduler starts.
     *
     * @throws Exception
     */
    @Test
    public void testManagerWhenSchedulerIsStarted()
        throws Exception {
      newConfigWithUpdatePeriodValue(1);
      final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
      final HttpServletResponse response = Context.get().getResponse();
      Mockito.when(request.getRequestURI()).thenReturn("/app/g1.css");
      Context.unset();
      Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)));
  
      managerFactory.create().process();
      // allow thread to do its job
      Thread.sleep(300);
    }
  
    @Test
    public void testAggregatedComputedFolder()
        throws Exception {
      final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
      final HttpServletResponse response = Context.get().getResponse();
      Mockito.when(request.getRequestURI()).thenReturn("/wro4j/wro/g1.css");
      Context.unset();
      Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)));
  
      managerFactory.create().process();
  
      Assert.assertEquals("/wro4j/wro/", Context.get().getAggregatedFolderPath());
    }
  
    @Test
    public void testAggregatedComputedFolder2()
        throws Exception {
      final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
      final HttpServletResponse response = Context.get().getResponse();
      Mockito.when(request.getRequestURI()).thenReturn("/wro4j/wro/path/to/g1.css");
      Context.unset();
      Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)));
  
      managerFactory.create().process();
  
      Assert.assertEquals("/wro4j/wro/path/to/", Context.get().getAggregatedFolderPath());
    }
  
    @Test(expected = WroRuntimeException.class)
    public void shouldNotProcessGroupWithNoResources()
        throws Exception {
      final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
      final HttpServletResponse response = Context.get().getResponse();
      Mockito.when(request.getRequestURI()).thenReturn("/noResources.css");
  
      final WroConfiguration config = new WroConfiguration();
      config.setIgnoreEmptyGroup(false);
      Context.unset();
      Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)), config);
  
      final WroModel model = new WroModel();
      model.addGroup(new Group("noResources"));
      final WroManagerFactory managerFactory = new BaseWroManagerFactory().setModelFactory(WroUtil.factoryFor(model));
      managerFactory.create().process();
    }
  
    @Test
    public void testCRC32Fingerprint()
        throws Exception {
      final WroManagerFactory factory = new BaseWroManagerFactory().setModelFactory(getValidModelFactory()).setHashStrategy(
          new CRC32HashStrategy());
      final WroManager manager = factory.create();
      final String path = manager.encodeVersionIntoGroupPath("g3", ResourceType.CSS, true);
      assertEquals("1d62dbaf/g3.css?minimize=true", path);
    }
  
    @Test
    public void testMD5Fingerprint()
        throws Exception {
      final WroManagerFactory factory = new BaseWroManagerFactory().setModelFactory(getValidModelFactory()).setHashStrategy(
          new MD5HashStrategy());
      final WroManager manager = factory.create();
      final String path = manager.encodeVersionIntoGroupPath("g3", ResourceType.CSS, true);
      assertEquals("9394d6cdd0a75a5f695c84eda410103f/g3.css?minimize=true", path);
    }
  
    @Test
    public void testSHA1DefaultHashBuilder()
        throws Exception {
      final WroManager manager = managerFactory.create();
      final String path = manager.encodeVersionIntoGroupPath("g3", ResourceType.CSS, true);
      assertEquals("aebaedcdec8131230d47259be7a628b5dfeff6ba/g3.css?minimize=true", path);
    }
  
    @Test
    public void cacheShouldNotBeClearedAfterModelReload()
        throws IOException {
      final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
      final HttpServletResponse response = Mockito.mock(HttpServletResponse.class, Mockito.RETURNS_DEEP_STUBS);
      Mockito.when(request.getRequestURI()).thenReturn("/app/g3.css");
  
      final WroConfiguration config = new WroConfiguration();
      config.setDebug(true);
      Context.unset();
      Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)));
  
      final WroManager wroManager = managerFactory.create();
      wroManager.process();
  
      // use original decorated object because the decorated one trigger the processing for each cache lookup.
      final CacheStrategy<CacheKey, CacheValue> cacheStrategy = AbstractDecorator.getOriginalDecoratedObject(wroManager.getCacheStrategy());
      Assert.assertNotNull(cacheStrategy.get(new CacheKey("g3", ResourceType.CSS, true)));
  
      final ReloadModelRunnable reloadModelRunnable = new ReloadModelRunnable(wroManager.getModelFactory());
      reloadModelRunnable.run();
      Assert.assertNotNull(cacheStrategy.get(new CacheKey("g3", ResourceType.CSS, true)));
    }
  
    @Test
    public void shouldRegisterCallback() {
      final LifecycleCallback mockCallback = Mockito.mock(LifecycleCallback.class);
      victim.registerCallback(new ObjectFactory<LifecycleCallback>() {
        public LifecycleCallback create() {
          return mockCallback;
        }
      });
      victim.getCallbackRegistry().onProcessingComplete();
      Mockito.verify(mockCallback, Mockito.atLeastOnce()).onProcessingComplete();
    }
  
    @Test
    public void shouldDestroyDependenciesWhenDestoryed() {
      final WroManager manager = new WroManager.Builder().setCacheStrategy(mockCacheStrategy).setModelFactory(
          mockModelFactory).build();
  
      manager.destroy();
  
      verify(mockCacheStrategy).destroy();
      verify(mockModelFactory).destroy();
    }
  
    private static class DestroyableProcessor
        extends PlaceholderProcessor
        implements Destroyable {
      public void destroy() {
      }
    }
  
    @Test
    public void shouldDestroyDestroyableProcessorWhenManagerIsDestroyed() {
      final DestroyableProcessor preProcessor = Mockito.mock(DestroyableProcessor.class);
      final DestroyableProcessor postProcessor = Mockito.mock(DestroyableProcessor.class);
      victim = new BaseWroManagerFactory().setProcessorsFactory(
          new SimpleProcessorsFactory().addPreProcessor(preProcessor).addPostProcessor(postProcessor)).create();
      victim.destroy();
      Mockito.verify(preProcessor, Mockito.times(1)).destroy();
      Mockito.verify(postProcessor, Mockito.times(1)).destroy();
    }
  
    @After
    public void tearDown() {
      managerFactory.destroy();
      Context.unset();
    }
  }
  