/* * 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 com.xpn.xwiki; import java.sql.Timestamp; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentMatcher; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.xwiki.bridge.event.DocumentDeletedEvent; import org.xwiki.bridge.event.DocumentDeletingEvent; import org.xwiki.bridge.event.DocumentRolledBackEvent; import org.xwiki.bridge.event.DocumentRollingBackEvent; import org.xwiki.bridge.event.DocumentUpdatedEvent; import org.xwiki.bridge.event.DocumentUpdatingEvent; import org.xwiki.configuration.ConfigurationSource; import org.xwiki.environment.Environment; import org.xwiki.localization.ContextualLocalizationManager; import org.xwiki.model.reference.AttachmentReference; import org.xwiki.model.reference.AttachmentReferenceResolver; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.DocumentReferenceResolver; import org.xwiki.model.reference.EntityReference; import org.xwiki.model.reference.EntityReferenceProvider; import org.xwiki.model.reference.EntityReferenceResolver; import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.model.reference.ObjectReferenceResolver; import org.xwiki.model.reference.SpaceReference; import org.xwiki.model.reference.WikiReference; import org.xwiki.observation.ObservationManager; import org.xwiki.rendering.syntax.SyntaxFactory; import org.xwiki.resource.ResourceReferenceManager; import org.xwiki.test.mockito.MockitoComponentManagerRule; import com.xpn.xwiki.doc.DocumentRevisionProvider; import com.xpn.xwiki.doc.XWikiAttachment; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.internal.XWikiCfgConfigurationSource; import com.xpn.xwiki.objects.BaseObject; import com.xpn.xwiki.store.AttachmentRecycleBinStore; import com.xpn.xwiki.store.XWikiRecycleBinStoreInterface; import com.xpn.xwiki.store.XWikiStoreInterface; import com.xpn.xwiki.store.XWikiVersioningStoreInterface; import com.xpn.xwiki.web.Utils; import com.xpn.xwiki.web.XWikiURLFactory; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; /** * Unit tests for {@link XWiki}. * * @version $Id: 80eb8156667198a3cc74f7b3c7de23f07f8b6af7 $ */ public class XWikiMockitoTest { /** * A component manager that allows us to register mock components. */ @Rule public MockitoComponentManagerRule mocker = new MockitoComponentManagerRule(); /** * The object being tested. */ private XWiki xwiki; /** * A mock {@link XWikiContext}; */ private XWikiContext context = new XWikiContext(); private ConfigurationSource xwikiCfgConfigurationSource; private XWikiStoreInterface storeMock; private DocumentRevisionProvider documentRevisionProvider; @Before public void setUp() throws Exception { this.mocker.registerMockComponent(EntityReferenceSerializer.TYPE_STRING); this.mocker.registerMockComponent(EntityReferenceSerializer.TYPE_STRING, "local"); this.mocker.registerMockComponent(EntityReferenceSerializer.TYPE_STRING, "compact"); this.mocker.registerMockComponent(EntityReferenceSerializer.TYPE_STRING, "compactwiki"); this.mocker.registerMockComponent(EntityReferenceSerializer.TYPE_STRING, "uid"); this.mocker.registerMockComponent(EntityReferenceSerializer.TYPE_STRING, "local/uid"); this.mocker.registerMockComponent(EntityReferenceResolver.TYPE_STRING, "relative"); this.mocker.registerMockComponent(EntityReferenceResolver.TYPE_STRING, "currentmixed"); this.mocker.registerMockComponent(EntityReferenceResolver.TYPE_STRING, "xclass"); this.mocker.registerMockComponent(DocumentReferenceResolver.TYPE_REFERENCE, "current"); this.mocker.registerMockComponent(DocumentReferenceResolver.TYPE_REFERENCE, "explicit"); this.mocker.registerMockComponent(DocumentReferenceResolver.TYPE_STRING, "current"); this.mocker.registerMockComponent(DocumentReferenceResolver.TYPE_STRING, "explicit"); this.mocker.registerMockComponent(DocumentReferenceResolver.TYPE_STRING, "currentmixed"); this.mocker.registerMockComponent(ObjectReferenceResolver.TYPE_REFERENCE, "current"); this.mocker.registerMockComponent(EntityReferenceProvider.class); this.mocker.registerMockComponent(SyntaxFactory.class); this.mocker.registerMockComponent(ResourceReferenceManager.class); this.mocker.registerMockComponent(Environment.class); this.mocker.registerMockComponent(ObservationManager.class); this.mocker.registerMockComponent(ConfigurationSource.class, XWikiCfgConfigurationSource.ROLEHINT); Utils.setComponentManager(mocker); xwiki = new XWiki(); this.context.setWiki(this.xwiki); this.storeMock = mock(XWikiStoreInterface.class); xwiki.setStore(storeMock); XWikiVersioningStoreInterface versioningStore = mock(XWikiVersioningStoreInterface.class); xwiki.setVersioningStore(versioningStore); this.documentRevisionProvider = this.mocker.registerMockComponent(DocumentRevisionProvider.class); this.xwikiCfgConfigurationSource = this.mocker.registerMockComponent(ConfigurationSource.class, "xwikicfg"); } /** * Verify that attachment versions are not incremented when a document is copied. * * @see <a href="https://jira.xwiki.org/browse/XWIKI-8157">XWIKI-8157: The "Copy Page" action adds an extra version * to the attached file</a> */ @Test public void copyDocumentPreservesAttachmentsVersion() throws Exception { DocumentReference targetReference = new DocumentReference("bar", "Space", "Target"); XWikiDocument target = mock(XWikiDocument.class); when(target.isNew()).thenReturn(true); when(target.getDocumentReference()).thenReturn(targetReference); DocumentReference sourceReference = new DocumentReference("foo", "Space", "Source"); XWikiDocument source = mock(XWikiDocument.class); when(source.copyDocument(targetReference, context)).thenReturn(target); when(xwiki.getStore().loadXWikiDoc(any(XWikiDocument.class), same(context))).thenReturn(source, target); Assert.assertTrue(xwiki.copyDocument(sourceReference, targetReference, context)); // The target document needs to be new in order for the attachment version to be preserved on save. verify(target).setNew(true); verify(xwiki.getStore()).saveXWikiDoc(target, context); } /** * Verify that {@link XWiki#rollback(XWikiDocument, String, XWikiContext)} fires the right events. */ @Test public void rollbackFiresEvents() throws Exception { ObservationManager observationManager = mocker.getInstance(ObservationManager.class); DocumentReference documentReference = new DocumentReference("wiki", "Space", "Page"); XWikiDocument document = mock(XWikiDocument.class); when(document.getDocumentReference()).thenReturn(documentReference); XWikiDocument originalDocument = mock(XWikiDocument.class); // Mark the document as existing so that the roll-back method will fire an update event. when(originalDocument.isNew()).thenReturn(false); XWikiDocument result = mock(XWikiDocument.class); when(result.clone()).thenReturn(result); when(result.getDocumentReference()).thenReturn(documentReference); when(result.getOriginalDocument()).thenReturn(originalDocument); String revision = "3.5"; when(this.documentRevisionProvider.getRevision(document, revision)).thenReturn(result); this.mocker.registerMockComponent(ContextualLocalizationManager.class); xwiki.rollback(document, revision, context); verify(observationManager).notify(new DocumentRollingBackEvent(documentReference, revision), result, context); verify(observationManager).notify(new DocumentUpdatingEvent(documentReference), result, context); verify(observationManager).notify(new DocumentUpdatedEvent(documentReference), result, context); verify(observationManager).notify(new DocumentRolledBackEvent(documentReference, revision), result, context); } /** * @see "XWIKI-9399: Attachment version is incremented when a document is rolled back even if the attachment did not * change" */ @Test public void rollbackDoesNotSaveUnchangedAttachment() throws Exception { String version = "1.1"; String fileName = "logo.png"; Date date = new Date(); XWikiAttachment currentAttachment = mock(XWikiAttachment.class); when(currentAttachment.getAttachmentRevision(version, context)).thenReturn(currentAttachment); when(currentAttachment.getDate()).thenReturn(new Timestamp(date.getTime())); when(currentAttachment.getVersion()).thenReturn(version); when(currentAttachment.getFilename()).thenReturn(fileName); XWikiAttachment oldAttachment = mock(XWikiAttachment.class); when(oldAttachment.getFilename()).thenReturn(fileName); when(oldAttachment.getVersion()).thenReturn(version); when(oldAttachment.getDate()).thenReturn(date); DocumentReference documentReference = new DocumentReference("wiki", "Space", "Page"); XWikiDocument document = mock(XWikiDocument.class); when(document.getDocumentReference()).thenReturn(documentReference); when(document.getAttachmentList()).thenReturn(Arrays.asList(currentAttachment)); when(document.getAttachment(fileName)).thenReturn(currentAttachment); XWikiDocument result = mock(XWikiDocument.class); when(result.clone()).thenReturn(result); when(result.getDocumentReference()).thenReturn(documentReference); when(result.getAttachmentList()).thenReturn(Arrays.asList(oldAttachment)); when(result.getAttachment(fileName)).thenReturn(oldAttachment); String revision = "3.5"; when(this.documentRevisionProvider.getRevision(document, revision)).thenReturn(result); AttachmentRecycleBinStore attachmentRecycleBinStore = mock(AttachmentRecycleBinStore.class); xwiki.setAttachmentRecycleBinStore(attachmentRecycleBinStore); DocumentReference reference = document.getDocumentReference(); this.mocker.registerMockComponent(ContextualLocalizationManager.class); when(xwiki.getStore().loadXWikiDoc(any(XWikiDocument.class), same(context))) .thenReturn(new XWikiDocument(reference)); xwiki.rollback(document, revision, context); verify(attachmentRecycleBinStore, never()).saveToRecycleBin(same(currentAttachment), any(String.class), any(Date.class), same(context), eq(true)); verify(oldAttachment, never()).setMetaDataDirty(true); } @Test public void deleteAllDocumentsAndWithoutSendingToTrash() throws Exception { XWiki xwiki = new XWiki(); XWikiDocument document = mock(XWikiDocument.class); DocumentReference reference = new DocumentReference("wiki", "space", "page"); when(document.getDocumentReference()).thenReturn(reference); // Make sure we have a trash for the test. XWikiRecycleBinStoreInterface recycleBinStoreInterface = mock(XWikiRecycleBinStoreInterface.class); xwiki.setRecycleBinStore(recycleBinStoreInterface); when(xwikiCfgConfigurationSource.getProperty("xwiki.recyclebin", "1")).thenReturn("1"); // Configure the mocked Store to later verify if it's called XWikiStoreInterface storeInterface = mock(XWikiStoreInterface.class); xwiki.setStore(storeInterface); XWikiContext xwikiContext = mock(XWikiContext.class); xwiki.deleteAllDocuments(document, false, xwikiContext); // Verify that saveToRecycleBin is never called since otherwise it would mean the doc has been saved in the // trash verify(recycleBinStoreInterface, never()).saveToRecycleBin(any(XWikiDocument.class), any(String.class), any(Date.class), any(XWikiContext.class), any(Boolean.class)); // Verify that deleteXWikiDoc() is called verify(storeInterface).deleteXWikiDoc(document, xwikiContext); } @Test public void deleteDocument() throws Exception { final DocumentReference documentReference = new DocumentReference("wiki", "Space", "Page"); XWikiDocument document = mock(XWikiDocument.class); when(document.getDocumentReference()).thenReturn(documentReference); final XWikiDocument originalDocument = mock(XWikiDocument.class); when(document.getOriginalDocument()).thenReturn(originalDocument); this.xwiki.deleteDocument(document, this.context); ObservationManager observation = this.mocker.getInstance(ObservationManager.class); ArgumentMatcher<XWikiDocument> matcher = new ArgumentMatcher<XWikiDocument>() { @Override public boolean matches(XWikiDocument argument) { return argument.getDocumentReference().equals(documentReference) && argument.getOriginalDocument() == originalDocument; } }; // Make sure the right events have been sent verify(observation).notify(eq(new DocumentDeletingEvent(documentReference)), argThat(matcher), same(this.context)); verify(observation).notify(eq(new DocumentDeletedEvent(documentReference)), argThat(matcher), same(this.context)); verifyNoMoreInteractions(observation); } @Test public void getPlainUserName() throws XWikiException { XWikiDocument document = mock(XWikiDocument.class); DocumentReference userReference = new DocumentReference("wiki", "XWiki", "user"); when(document.getDocumentReference()).thenReturn(userReference); when(this.storeMock.loadXWikiDoc(any(XWikiDocument.class), any(XWikiContext.class))).thenReturn(document); BaseObject userObject = mock(BaseObject.class); when(document.getObject("XWiki.XWikiUsers")).thenReturn(userObject); when(userObject.getStringValue("first_name")).thenReturn("first<name"); when(userObject.getStringValue("last_name")).thenReturn("last'name"); assertEquals("first<name last'name", xwiki.getPlainUserName(userReference, context)); when(userObject.getStringValue("first_name")).thenReturn("first<name"); when(userObject.getStringValue("last_name")).thenReturn(""); assertEquals("first<name", xwiki.getPlainUserName(userReference, context)); when(userObject.getStringValue("first_name")).thenReturn(""); when(userObject.getStringValue("last_name")).thenReturn("last'name"); assertEquals("last'name", xwiki.getPlainUserName(userReference, context)); } @Test public void getURLWithDotsAndBackslashInSpaceName() throws Exception { XWikiURLFactory urlFactory = mock(XWikiURLFactory.class); context.setURLFactory(urlFactory); DocumentReference reference = new DocumentReference("wiki", Arrays.asList("space.withdot.and\\and:"), "page"); EntityReferenceSerializer<String> serializer = this.mocker.getInstance(EntityReferenceSerializer.TYPE_STRING, "local"); when(serializer.serialize(reference.getLastSpaceReference())).thenReturn("somescapedspace"); this.xwiki.getURL(reference, "view", null, null, context); verify(urlFactory).createURL("somescapedspace", "page", "view", null, null, "wiki", context); } @Test public void getURLWithLocale() throws Exception { XWikiURLFactory urlFactory = mock(XWikiURLFactory.class); context.setURLFactory(urlFactory); DocumentReference reference = new DocumentReference("wiki", "Space", "Page", Locale.FRENCH); EntityReferenceSerializer<String> serializer = this.mocker.getInstance(EntityReferenceSerializer.TYPE_STRING, "local"); when(serializer.serialize(reference.getLastSpaceReference())).thenReturn("Space"); this.xwiki.getURL(reference, "view", null, null, context); verify(urlFactory).createURL("Space", "Page", "view", "language=fr", null, "wiki", context); this.xwiki.getURL(reference, "view", "language=ro", null, context); verify(urlFactory).createURL("Space", "Page", "view", "language=ro&language=fr", null, "wiki", context); } @Test public void getEntityURLWithDefaultAction() throws Exception { DocumentReference documentReference = new DocumentReference("tennis", Arrays.asList("Path", "To"), "Success"); AttachmentReference attachmentReference = new AttachmentReference("image.png", documentReference); XWikiURLFactory urlFactory = mock(XWikiURLFactory.class); context.setURLFactory(urlFactory); EntityReferenceSerializer<String> localSerializer = this.mocker.getInstance(EntityReferenceSerializer.TYPE_STRING, "local"); when(localSerializer.serialize(documentReference.getLastSpaceReference())).thenReturn("Path.To"); // Document Entity DocumentReferenceResolver<EntityReference> documentResolver = this.mocker.registerMockComponent(DocumentReferenceResolver.TYPE_REFERENCE, "currentgetdocument"); when(documentResolver.resolve(documentReference)).thenReturn(documentReference); this.xwiki.getURL(documentReference, this.context); verify(urlFactory).createURL("Path.To", "Success", "view", null, null, "tennis", this.context); // Attachment Entity AttachmentReferenceResolver<EntityReference> attachmentResolver = this.mocker.registerMockComponent(AttachmentReferenceResolver.TYPE_REFERENCE, "current"); when(attachmentResolver.resolve(attachmentReference)).thenReturn(attachmentReference); this.xwiki.getURL(attachmentReference, this.context); verify(urlFactory).createAttachmentURL("image.png", "Path.To", "Success", "download", null, "tennis", this.context); } @Test public void getSpacePreference() throws Exception { this.mocker.registerMockComponent(ConfigurationSource.class, "wiki"); ConfigurationSource spaceConfiguration = this.mocker.registerMockComponent(ConfigurationSource.class, "space"); when(this.xwikiCfgConfigurationSource.getProperty(any(), anyString())).then(new Answer<String>() { @Override public String answer(InvocationOnMock invocation) throws Throwable { return invocation.getArgument(1); } }); WikiReference wikiReference = new WikiReference("wiki"); SpaceReference space1Reference = new SpaceReference("space1", wikiReference); SpaceReference space2Reference = new SpaceReference("space2", space1Reference); // Without preferences and current doc assertEquals("", this.xwiki.getSpacePreference("pref", this.context)); assertEquals("defaultvalue", this.xwiki.getSpacePreference("pref", "defaultvalue", this.context)); assertEquals("", this.xwiki.getSpacePreference("pref", space2Reference, this.context)); assertEquals("defaultvalue", this.xwiki.getSpacePreference("pref", space2Reference, "defaultvalue", this.context)); // Without preferences but with current doc this.context.setDoc(new XWikiDocument(new DocumentReference("document", space2Reference))); assertEquals("", this.xwiki.getSpacePreference("pref", this.context)); assertEquals("defaultvalue", this.xwiki.getSpacePreference("pref", "defaultvalue", this.context)); assertEquals("", this.xwiki.getSpacePreference("pref", space2Reference, this.context)); assertEquals("defaultvalue", this.xwiki.getSpacePreference("pref", space2Reference, "defaultvalue", this.context)); // With preferences final Map<String, Map<String, String>> spacesPreferences = new HashMap<>(); Map<String, String> space1Preferences = new HashMap<>(); space1Preferences.put("pref", "prefvalue1"); space1Preferences.put("pref1", "pref1value1"); Map<String, String> space2Preferences = new HashMap<>(); space2Preferences.put("pref", "prefvalue2"); space2Preferences.put("pref2", "pref2value2"); spacesPreferences.put(space1Reference.getName(), space1Preferences); spacesPreferences.put(space2Reference.getName(), space2Preferences); when(spaceConfiguration.getProperty(any(), same(String.class))).then(new Answer<String>() { @Override public String answer(InvocationOnMock invocation) throws Throwable { if (context.getDoc() != null) { Map<String, String> spacePreferences = spacesPreferences.get(context.getDoc().getDocumentReference().getParent().getName()); if (spacePreferences != null) { return spacePreferences.get(invocation.getArgument(0)); } } return null; } }); this.context.setDoc(new XWikiDocument(new DocumentReference("document", space1Reference))); assertEquals("prefvalue1", this.xwiki.getSpacePreference("pref", this.context)); assertEquals("prefvalue1", this.xwiki.getSpacePreference("pref", "defaultvalue", this.context)); assertEquals("pref1value1", this.xwiki.getSpacePreference("pref1", this.context)); assertEquals("", this.xwiki.getSpacePreference("pref2", this.context)); this.context.setDoc(new XWikiDocument(new DocumentReference("document", space2Reference))); assertEquals("prefvalue2", this.xwiki.getSpacePreference("pref", this.context)); assertEquals("prefvalue2", this.xwiki.getSpacePreference("pref", "defaultvalue", this.context)); assertEquals("pref1value1", this.xwiki.getSpacePreference("pref1", this.context)); assertEquals("pref2value2", this.xwiki.getSpacePreference("pref2", this.context)); assertEquals("", this.xwiki.getSpacePreference("nopref", space1Reference, this.context)); assertEquals("defaultvalue", this.xwiki.getSpacePreference("nopref", space1Reference, "defaultvalue", this.context)); assertEquals("prefvalue1", this.xwiki.getSpacePreference("pref", space1Reference, this.context)); assertEquals("prefvalue1", this.xwiki.getSpacePreference("pref", space1Reference, "defaultvalue", this.context)); assertEquals("pref1value1", this.xwiki.getSpacePreference("pref1", space1Reference, this.context)); assertEquals("", this.xwiki.getSpacePreference("pref2", space1Reference, this.context)); assertEquals("", this.xwiki.getSpacePreference("nopref", space2Reference, this.context)); assertEquals("defaultvalue", this.xwiki.getSpacePreference("nopref", space2Reference, "defaultvalue", this.context)); assertEquals("prefvalue2", this.xwiki.getSpacePreference("pref", space2Reference, this.context)); assertEquals("prefvalue2", this.xwiki.getSpacePreference("pref", space2Reference, "defaultvalue", this.context)); assertEquals("pref1value1", this.xwiki.getSpacePreference("pref1", space2Reference, this.context)); assertEquals("pref2value2", this.xwiki.getSpacePreference("pref2", space2Reference, this.context)); } }