/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.rendering.internal.macro;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.hamcrest.collection.IsArray;
import org.jmock.Expectations;
import org.jmock.api.Invocation;
import org.jmock.lib.action.CustomAction;
import org.junit.Assert;
import org.junit.Test;
import org.xwiki.bridge.DocumentModelBridge;
import org.xwiki.context.Execution;
import org.xwiki.display.internal.DocumentDisplayer;
import org.xwiki.display.internal.DocumentDisplayerParameters;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.rendering.block.Block;
import org.xwiki.rendering.block.MacroBlock;
import org.xwiki.rendering.block.MetaDataBlock;
import org.xwiki.rendering.block.XDOM;
import org.xwiki.rendering.internal.macro.display.DisplayMacro;
import org.xwiki.rendering.internal.transformation.macro.MacroTransformation;
import org.xwiki.rendering.listener.MetaData;
import org.xwiki.rendering.macro.Macro;
import org.xwiki.rendering.macro.MacroExecutionException;
import org.xwiki.rendering.macro.display.DisplayMacroParameters;
import org.xwiki.rendering.macro.script.ScriptMockSetup;
import org.xwiki.rendering.parser.Parser;
import org.xwiki.rendering.renderer.PrintRendererFactory;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.rendering.transformation.MacroTransformationContext;
import org.xwiki.rendering.transformation.Transformation;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.test.jmock.AbstractComponentTestCase;
import org.xwiki.velocity.VelocityManager;
import static org.xwiki.rendering.test.BlockAssert.assertBlocks;
/**
* Unit tests for {@link DisplayMacro}.
*
* @version $Id: d194a64b11554f5a350528e6f2ed8674a964b2bb $
*/
public class DisplayMacroTest extends AbstractComponentTestCase
{
private ScriptMockSetup mockSetup;
private DisplayMacro displayMacro;
private PrintRendererFactory rendererFactory;
/**
* Mocks the component that is used to resolve the 'reference' parameter.
*/
private DocumentReferenceResolver<String> mockDocumentReferenceResolver;
private AuthorizationManager mockAuthorization;
private DocumentModelBridge mockDocument;
@Override
public void setUp() throws Exception
{
super.setUp();
// Put a fake XWiki context on the execution context.
Execution execution = getComponentManager().getInstance(Execution.class);
execution.getContext().setProperty("xwikicontext", new HashMap<Object, Object>());
}
@Override
protected void registerComponents() throws Exception
{
super.registerComponents();
this.mockSetup = new ScriptMockSetup(getMockery(), getComponentManager());
this.mockDocumentReferenceResolver =
registerMockComponent(DocumentReferenceResolver.TYPE_STRING, "macro", "macroDocumentReferenceResolver");
this.mockAuthorization = registerMockComponent(AuthorizationManager.class);
this.displayMacro = (DisplayMacro) getComponentManager().getInstance(Macro.class, "display");
this.rendererFactory = getComponentManager().getInstance(PrintRendererFactory.class, "event/1.0");
}
@Test
public void testDisplayMacroShowsVelocityMacrosAreIsolated() throws Exception
{
String expected =
"beginDocument\n" + "beginMetaData [[base]=[wiki:Space.DisplayedPage][source]=[wiki:Space.DisplayedPage]"
+ "[syntax]=[XWiki 2.0]]\n" + "beginMacroMarkerStandalone [velocity] [] [#testmacro]\n"
+ "beginParagraph\n" + "onSpecialSymbol [#]\n" + "onWord [testmacro]\n" + "endParagraph\n"
+ "endMacroMarkerStandalone [velocity] [] [#testmacro]\n"
+ "endMetaData [[base]=[wiki:Space.DisplayedPage][source]=[wiki:Space.DisplayedPage]"
+ "[syntax]=[XWiki 2.0]]\n" + "endDocument";
// We verify that a Velocity macro set in the including page is not seen in the displayed page.
List<Block> blocks =
runDisplayMacroWithPreVelocity("#macro(testmacro)#end", "{{velocity}}#testmacro{{/velocity}}");
assertBlocks(expected, blocks, this.rendererFactory);
}
@Test
public void testDisplayMacroWithNoDocumentSpecified() throws Exception
{
DisplayMacroParameters parameters = new DisplayMacroParameters();
try {
this.displayMacro.execute(parameters, null, createMacroTransformationContext("whatever", false));
Assert.fail("An exception should have been thrown");
} catch (MacroExecutionException expected) {
Assert.assertEquals("You must specify a 'reference' parameter pointing to the entity to display.",
expected.getMessage());
}
}
/**
* Verify that relative links returned by the display macro as wrapped with a MetaDataBlock.
*/
@Test
public void testDisplayMacroWhenDisplayingDocumentWithRelativeReferences() throws Exception
{
String expected = "beginDocument\n" + "beginMetaData [[base]=[displayedWiki:displayedSpace.displayedPage]"
+ "[source]=[displayedWiki:displayedSpace.displayedPage][syntax]=[XWiki 2.0]]\n" + "beginParagraph\n"
+ "beginLink [Typed = [false] Type = [doc] Reference = [page]] [false]\n"
+ "endLink [Typed = [false] Type = [doc] Reference = [page]] [false]\n" + "onSpace\n"
+ "beginLink [Typed = [true] Type = [attach] Reference = [test.png]] [false]\n"
+ "endLink [Typed = [true] Type = [attach] Reference = [test.png]] [false]\n" + "onSpace\n"
+ "onImage [Typed = [false] Type = [attach] Reference = [test.png]] [true]\n" + "endParagraph\n"
+ "endMetaData [[base]=[displayedWiki:displayedSpace.displayedPage]"
+ "[source]=[displayedWiki:displayedSpace.displayedPage][syntax]=[XWiki 2.0]]\n" + "endDocument";
final DocumentReference displayedDocumentReference =
new DocumentReference("displayedWiki", "displayedSpace", "displayedPage");
setUpDocumentMock("displayedWiki:displayedSpace.displayedPage", displayedDocumentReference,
"[[page]] [[attach:test.png]] image:test.png");
getMockery().checking(new Expectations()
{
{
oneOf(mockSetup.bridge).isDocumentViewable(with(any(DocumentReference.class)));
will(returnValue(true));
oneOf(mockSetup.bridge).pushDocumentInContext(with(any(Map.class)),
with(any(DocumentModelBridge.class)));
oneOf(mockSetup.bridge).getCurrentDocumentReference();
will(returnValue(displayedDocumentReference));
oneOf(mockSetup.bridge).popDocumentFromContext(with(any(Map.class)));
}
});
DisplayMacroParameters parameters = new DisplayMacroParameters();
parameters.setReference("displayedWiki:displayedSpace.displayedPage");
List<Block> blocks =
this.displayMacro.execute(parameters, null, createMacroTransformationContext("whatever", false));
assertBlocks(expected, blocks, this.rendererFactory);
}
private static class ExpectedRecursiveInclusionException extends RuntimeException
{
}
@Test
public void testDisplayMacroWithRecursiveDisplay() throws Exception
{
final DocumentDisplayer mockDocumentDisplayer = getMockery().mock(DocumentDisplayer.class);
this.displayMacro.setDocumentAccessBridge(mockSetup.bridge);
FieldUtils.writeField(this.displayMacro, "documentDisplayer", mockDocumentDisplayer, true);
final MacroTransformationContext macroContext = createMacroTransformationContext("wiki:space.page", false);
final DisplayMacroParameters parameters = new DisplayMacroParameters();
parameters.setReference("wiki:space.page");
getMockery().checking(new Expectations()
{
{
allowing(mockDocumentReferenceResolver).resolve("wiki:space.page", macroContext.getCurrentMacroBlock());
will(returnValue(new DocumentReference("wiki", "space", "page")));
allowing(mockSetup.bridge).isDocumentViewable(with(any(DocumentReference.class)));
will(returnValue(true));
allowing(mockSetup.bridge).getDocument(with(any(DocumentReference.class)));
will(returnValue(null));
allowing(mockDocumentDisplayer).display(with(same((DocumentModelBridge) null)),
with(any(DocumentDisplayerParameters.class)));
will(new CustomAction("recursively call the include macro again")
{
@Override
public Object invoke(Invocation invocation) throws Throwable
{
try {
displayMacro.execute(parameters, null, macroContext);
} catch (Exception expected) {
if (expected.getMessage().contains("Found recursive display")) {
throw new ExpectedRecursiveInclusionException();
}
}
return true;
}
});
}
});
try {
this.displayMacro.execute(parameters, null, macroContext);
Assert.fail("The display macro hasn't checked the recursive display");
} catch (MacroExecutionException expected) {
if (!(expected.getCause() instanceof ExpectedRecursiveInclusionException)) {
throw expected;
}
}
}
@Test
public void testDisplayMacroInsideBaseMetaDataBlockAndWithRelativeDocumentReferencePassed() throws Exception
{
String expected = "beginDocument\n"
+ "beginMetaData [[base]=[wiki:space.relativePage][source]=[wiki:space.relativePage][syntax]=[XWiki 2.0]]\n"
+ "beginParagraph\n" + "onWord [content]\n" + "endParagraph\n"
+ "endMetaData [[base]=[wiki:space.relativePage][source]=[wiki:space.relativePage][syntax]=[XWiki 2.0]]\n"
+ "endDocument";
DisplayMacroParameters parameters = new DisplayMacroParameters();
parameters.setReference("relativePage");
MacroTransformationContext macroContext = createMacroTransformationContext("whatever", false);
// Add a Source MetaData Block as a parent of the display Macro block.
new MetaDataBlock(Collections.<Block>singletonList(macroContext.getCurrentMacroBlock()),
new MetaData(Collections.<String, Object>singletonMap(MetaData.BASE, "wiki:space.page")));
final DocumentReference sourceReference = new DocumentReference("wiki", "space", "page");
final DocumentReference resolvedReference = new DocumentReference("wiki", "space", "relativePage");
setUpDocumentMock("relativePage", resolvedReference, "content");
getMockery().checking(new Expectations()
{
{
allowing(mockDocumentReferenceResolver).resolve(with("wiki:space.page"),
with(IsArray.array(any(MacroBlock.class))));
will(returnValue(sourceReference));
allowing(mockDocumentReferenceResolver).resolve(with("relativePage"),
with(IsArray.array(any(MacroBlock.class))));
will(returnValue(resolvedReference));
oneOf(mockSetup.bridge).isDocumentViewable(resolvedReference);
will(returnValue(true));
oneOf(mockSetup.bridge).pushDocumentInContext(with(any(Map.class)), with(same(mockDocument)));
oneOf(mockSetup.bridge).getCurrentDocumentReference();
will(returnValue(resolvedReference));
oneOf(mockSetup.bridge).popDocumentFromContext(with(any(Map.class)));
}
});
List<Block> blocks = this.displayMacro.execute(parameters, null, macroContext);
assertBlocks(expected, blocks, this.rendererFactory);
}
@Test
public void testDisplayMacroWhenSectionSpecified() throws Exception
{
String expected =
"beginDocument\n" + "beginMetaData [[base]=[wiki:Space.DisplayedPage][source]=[wiki:Space.DisplayedPage]"
+ "[syntax]=[XWiki 2.0]]\n" + "beginHeader [1, Hsection]\n" + "onWord [section]\n"
+ "endHeader [1, Hsection]\n" + "beginParagraph\n" + "onWord [content2]\n" + "endParagraph\n"
+ "endMetaData [[base]=[wiki:Space.DisplayedPage][source]=[wiki:Space.DisplayedPage]"
+ "[syntax]=[XWiki 2.0]]\n" + "endDocument";
DisplayMacroParameters parameters = new DisplayMacroParameters();
parameters.setSection("Hsection");
List<Block> blocks = runDisplayMacro(parameters, "content1\n\n= section =\ncontent2");
assertBlocks(expected, blocks, this.rendererFactory);
}
@Test
public void testDisplayMacroWhenInvalidSectionSpecified() throws Exception
{
DisplayMacroParameters parameters = new DisplayMacroParameters();
parameters.setSection("unknown");
try {
runDisplayMacro(parameters, "content");
Assert.fail("Should have raised an exception");
} catch (MacroExecutionException expected) {
Assert.assertEquals("Cannot find section [unknown] in document [wiki:Space.DisplayedPage]",
expected.getMessage());
}
}
private MacroTransformationContext createMacroTransformationContext(String documentName, boolean isInline)
{
MacroTransformationContext context = new MacroTransformationContext();
MacroBlock displayMacro =
new MacroBlock("display", Collections.singletonMap("reference", documentName), isInline);
context.setCurrentMacroBlock(displayMacro);
return context;
}
private void setUpDocumentMock(final String resolve, final DocumentReference reference, final String content)
throws Exception
{
mockDocument = getMockery().mock(DocumentModelBridge.class, resolve);
getMockery().checking(new Expectations()
{
{
allowing(mockDocumentReferenceResolver).resolve(with(resolve),
with(IsArray.array(any(MacroBlock.class))));
will(returnValue(reference));
allowing(mockSetup.bridge).getDocument(reference);
will(returnValue(mockDocument));
allowing(mockDocument).getSyntax();
will(returnValue(Syntax.XWIKI_2_0));
allowing(mockDocument).getXDOM();
will(returnValue(getXDOM(content)));
allowing(mockDocument).getDocumentReference();
will(returnValue(reference));
}
});
}
private XDOM getXDOM(String content) throws Exception
{
Parser xwiki20Parser = getComponentManager().getInstance(Parser.class, "xwiki/2.0");
return xwiki20Parser.parse(new StringReader(content));
}
private List<Block> runDisplayMacroWithPreVelocity(String velocity, String displayedContent) throws Exception
{
VelocityManager velocityManager = getComponentManager().getInstance(VelocityManager.class);
StringWriter writer = new StringWriter();
velocityManager.getVelocityEngine().evaluate(velocityManager.getVelocityContext(), writer,
"wiki:Space.DisplayingPage", velocity);
return runDisplayMacro(displayedContent);
}
private List<Block> runDisplayMacro(String displayedContent) throws Exception
{
return runDisplayMacro(new DisplayMacroParameters(), displayedContent);
}
private List<Block> runDisplayMacro(DisplayMacroParameters parameters, String displayedContent) throws Exception
{
final DocumentReference displayedDocumentReference = new DocumentReference("wiki", "Space", "DisplayedPage");
String displayedDocStringRef = "wiki:space.page";
setUpDocumentMock(displayedDocStringRef, displayedDocumentReference, displayedContent);
getMockery().checking(new Expectations()
{
{
allowing(mockSetup.bridge).isDocumentViewable(with(same(displayedDocumentReference)));
will(returnValue(true));
oneOf(mockSetup.bridge).pushDocumentInContext(with(any(Map.class)), with(same(mockDocument)));
atMost(1).of(mockSetup.bridge).getCurrentDocumentReference();
will(returnValue(displayedDocumentReference));
oneOf(mockSetup.bridge).popDocumentFromContext(with(any(Map.class)));
}
});
this.displayMacro.setDocumentAccessBridge(this.mockSetup.bridge);
parameters.setReference(displayedDocStringRef);
// Create a Macro transformation context with the Macro transformation object defined so that the display
// macro can transform displayed page which is using a new context.
MacroTransformation macroTransformation =
(MacroTransformation) getComponentManager().getInstance(Transformation.class, "macro");
MacroTransformationContext macroContext = createMacroTransformationContext(displayedDocStringRef, false);
macroContext.setId("wiki:Space.DisplayingPage");
macroContext.setTransformation(macroTransformation);
return this.displayMacro.execute(parameters, null, macroContext);
}
}