From b5a95c4d14a5ebe88f16be3f072ab4f96f166431 Mon Sep 17 00:00:00 2001
From: Marius Dumitru Florea <marius@xwiki.com>
Date: Thu, 8 Jun 2023 01:07:46 +0300
Subject: [PATCH] XWIKI-20897: Cannot export with client-side PDF a given
 revision of a page XWIKI-20901: Add automated test for "Export a revision of
 a page"

(cherry picked from commit d28e8feb83575b7fe653cc6da7db9b1610964e0c)
---
 xwiki-platform-core/pom.xml                   | 10 ++++
 .../xwiki/bridge/DocumentAccessBridge.java    |  8 +++
 .../pdf/internal/job/DocumentRenderer.java    | 16 ++++-
 .../internal/job/DocumentRendererTest.java    | 15 +++++
 .../xwiki/export/pdf/test/ui/PDFExportIT.java | 59 +++++++++++++++++++
 .../resources/XWiki/PDFExport/WebHome.xml     | 11 ++++
 .../doc/DefaultDocumentAccessBridge.java      | 14 +++--
 .../context/XWikiContextContextStore.java     | 49 +++++++++++++--
 .../doc/DefaultDocumentAccessBridgeTest.java  | 13 ++++
 .../context/XWikiContextContextStoreTest.java | 41 +++++++++++++
 10 files changed, 224 insertions(+), 12 deletions(-)

diff --git a/xwiki-platform-core/pom.xml b/xwiki-platform-core/pom.xml
index e6219b36e26..eeee6a800f3 100644
--- a/xwiki-platform-core/pom.xml
+++ b/xwiki-platform-core/pom.xml
@@ -153,6 +153,16 @@
                 </item>
               </differences>
             </revapi.differences>
+            <revapi.differences>
+              <differences>
+                <item>
+                  <code>java.method.addedToInterface</code>
+                  <new>method org.xwiki.bridge.DocumentModelBridge org.xwiki.bridge.DocumentAccessBridge::getCurrentDocument()</new>
+                  <justification>This change was necessary in order to fix XWIKI-20897 (Cannot export with client-side PDF a given revision of a page)</justification>
+                  <criticality>highlight</criticality>
+                </item>
+              </differences>
+            </revapi.differences>
           </analysisConfiguration>
         </configuration>
       </plugin>
diff --git a/xwiki-platform-core/xwiki-platform-bridge/src/main/java/org/xwiki/bridge/DocumentAccessBridge.java b/xwiki-platform-core/xwiki-platform-bridge/src/main/java/org/xwiki/bridge/DocumentAccessBridge.java
index de8dd4b02ed..b185c0813c9 100644
--- a/xwiki-platform-core/xwiki-platform-bridge/src/main/java/org/xwiki/bridge/DocumentAccessBridge.java
+++ b/xwiki-platform-core/xwiki-platform-bridge/src/main/java/org/xwiki/bridge/DocumentAccessBridge.java
@@ -169,6 +169,14 @@ default DocumentModelBridge getTranslatedDocumentInstance(EntityReference entity
      */
     DocumentReference getCurrentDocumentReference();
 
+    /**
+     * @return the current document on the XWiki context or {@code null} if there's no document set on the XWiki context
+     * @since 14.10.2
+     * @since 15.5RC1
+     */
+    @Unstable
+    DocumentModelBridge getCurrentDocument();
+
     /**
      * Check if a document exists or not in the wiki.
      * <p>
diff --git a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/main/java/org/xwiki/export/pdf/internal/job/DocumentRenderer.java b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/main/java/org/xwiki/export/pdf/internal/job/DocumentRenderer.java
index 881404eb31e..4f90819b2af 100644
--- a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/main/java/org/xwiki/export/pdf/internal/job/DocumentRenderer.java
+++ b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/main/java/org/xwiki/export/pdf/internal/job/DocumentRenderer.java
@@ -107,12 +107,24 @@ public DocumentRenderingResult render(DocumentReference documentReference, boole
         // included in the same parent document).
         parameters.setIdGenerator(this.idGenerator);
 
-        DocumentModelBridge document = this.documentAccessBridge.getTranslatedDocumentInstance(documentReference);
-        XDOM xdom = display(document, parameters, withTitle);
+        XDOM xdom = display(getDocument(documentReference), parameters, withTitle);
         String html = renderXDOM(xdom, targetSyntax);
         return new DocumentRenderingResult(documentReference, xdom, html, this.idGenerator.resetLocalIds());
     }
 
+    private DocumentModelBridge getDocument(DocumentReference documentReference) throws Exception
+    {
+        // If the current document is included in the PDF export then we use the document instance from the XWiki
+        // context in order to match what the user sees in view mode (e.g. the user might be looking at a document
+        // revision).
+        DocumentModelBridge currentDocument = this.documentAccessBridge.getCurrentDocument();
+        if (currentDocument != null && documentReference.equals(currentDocument.getDocumentReference())) {
+            return currentDocument;
+        } else {
+            return this.documentAccessBridge.getTranslatedDocumentInstance(documentReference);
+        }
+    }
+
     private XDOM display(DocumentModelBridge document, DocumentDisplayerParameters parameters, boolean withTitle)
     {
         XDOM xdom = this.documentDisplayer.display(document, parameters);
diff --git a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/test/java/org/xwiki/export/pdf/internal/job/DocumentRendererTest.java b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/test/java/org/xwiki/export/pdf/internal/job/DocumentRendererTest.java
index 1aa8f315823..0a819c0b373 100644
--- a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/test/java/org/xwiki/export/pdf/internal/job/DocumentRendererTest.java
+++ b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-api/src/test/java/org/xwiki/export/pdf/internal/job/DocumentRendererTest.java
@@ -56,6 +56,8 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -153,4 +155,17 @@ public Void answer(InvocationOnMock invocation)
         expectedIdMap.put("Hheading-1", "Hheading-2");
         assertEquals(expectedIdMap, result.getIdMap());
     }
+
+    @Test
+    void renderCurrentDocument() throws Exception
+    {
+        DocumentReference currentDocumentReference = new DocumentReference("test", "Some", "Page");
+        DocumentModelBridge currentDocument = mock(DocumentModelBridge.class);
+        when(currentDocument.getDocumentReference()).thenReturn(currentDocumentReference);
+        when(this.documentAccessBridge.getCurrentDocument()).thenReturn(currentDocument);
+
+        this.documentRenderer.render(currentDocumentReference, false);
+
+        verify(this.documentDisplayer).display(same(currentDocument), any(DocumentDisplayerParameters.class));
+    }
 }
diff --git a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-test/xwiki-platform-export-pdf-test-docker/src/test/it/org/xwiki/export/pdf/test/ui/PDFExportIT.java b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-test/xwiki-platform-export-pdf-test-docker/src/test/it/org/xwiki/export/pdf/test/ui/PDFExportIT.java
index 50c8565498c..e98d106be0e 100644
--- a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-test/xwiki-platform-export-pdf-test-docker/src/test/it/org/xwiki/export/pdf/test/ui/PDFExportIT.java
+++ b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-test/xwiki-platform-export-pdf-test-docker/src/test/it/org/xwiki/export/pdf/test/ui/PDFExportIT.java
@@ -700,6 +700,65 @@ void resizedTable(TestUtils setup, TestConfiguration testConfiguration) throws E
         }
     }
 
+    @Test
+    @Order(14)
+    void pageRevision(TestUtils setup, TestReference testReference, TestConfiguration testConfiguration) throws Exception
+    {
+        setup.createPage(testReference, "Parent initial content.", "Parent Initial Title");
+        setup.rest().savePage(testReference, "Parent modified content.", "Parent Modified Title");
+
+        EntityReference childReference =
+            new EntityReference("Child", EntityType.DOCUMENT, testReference.getLastSpaceReference());
+        setup.createPage(childReference, "Child initial content.", "Child Initial Title");
+        setup.rest().savePage(childReference, "Child modified content.", "Child Modified Title");
+
+        ViewPage viewPage = setup.gotoPage(testReference);
+        assertEquals("Parent modified content.", viewPage.getContent());
+
+        viewPage = viewPage.openHistoryDocExtraPane().viewVersion("1.1");
+        assertEquals("Parent initial content.", viewPage.getContent());
+
+        ExportTreeModal exportTreeModal = ExportTreeModal.open(viewPage, "PDF");
+        // Include the child page in the export because we want to check that the revision specified in the query string
+        // applies only to the current page.
+        exportTreeModal.getPageTree().getNode("document:" + setup.serializeReference(childReference)).select();
+        exportTreeModal.export();
+
+        try (PDFDocument pdf = export(new PDFExportOptionsModal(), testConfiguration)) {
+            // We should have 4 pages: cover page, table of contents, one page for the parent document and one page for
+            // the child document.
+            assertEquals(4, pdf.getNumberOfPages());
+
+            //
+            // Verify the cover page.
+            //
+
+            String coverPageText = pdf.getTextFromPage(0);
+            assertTrue(coverPageText.startsWith("Parent Initial Title\nVersion 1.1 authored by John"),
+                "Unexpected cover page text: " + coverPageText);
+
+            //
+            // Verify the page corresponding to the parent document.
+            //
+
+            String contentPageText = pdf.getTextFromPage(2);
+            assertTrue(contentPageText.startsWith("Parent Initial Title\n3 / 4\n"),
+                "Unexpected header and footer on the content page: " + contentPageText);
+            assertTrue(contentPageText.contains("Parent Initial Title\nParent initial content.\n"),
+                "Unexpected parent page content: " + contentPageText);
+
+            //
+            // Verify the page corresponding to the child document.
+            //
+
+            contentPageText = pdf.getTextFromPage(3);
+            assertTrue(contentPageText.startsWith("Child Modified Title\n4 / 4\n"),
+                "Unexpected header and footer on the content page: " + contentPageText);
+            assertTrue(contentPageText.contains("Child Modified Title\nChild modified content.\n"),
+                "Unexpected child page content: " + contentPageText);
+        }
+    }
+
     private URL getHostURL(TestConfiguration testConfiguration) throws Exception
     {
         return new URL(String.format("http://%s:%d", testConfiguration.getServletEngine().getIP(),
diff --git a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-ui/src/main/resources/XWiki/PDFExport/WebHome.xml b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-ui/src/main/resources/XWiki/PDFExport/WebHome.xml
index 782409c9101..2b4e1cb32b5 100644
--- a/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-ui/src/main/resources/XWiki/PDFExport/WebHome.xml
+++ b/xwiki-platform-core/xwiki-platform-export/xwiki-platform-export-pdf/xwiki-platform-export-pdf-ui/src/main/resources/XWiki/PDFExport/WebHome.xml
@@ -370,11 +370,15 @@
       resolvePDFExportJobStarted = resolve;
       rejectPDFExportJobStarted = reject;
     });
+    const locale = document.documentElement.getAttribute('lang') || '';
     data.push(
       {name: 'outputSyntax', value: 'plain'},
       {name: 'sheet', value: 'XWiki.PDFExport.WebHome'},
       {name: 'action', value: 'export'},
       {name: 'form_token', value: xwikiMeta.form_token},
+      // We pass the locale in order to make sure the document saved in the PDF export job context (when the job is
+      // scheduled) matches the current document translation.
+      {name: 'language', value: locale},
       // We add the current value of the query string because we want to export the current state of the page (the query
       // string can hold for instance the state of the live data component).
       {name: 'pdfQueryString', value: window.location.search.substring(1)},
@@ -382,6 +386,13 @@
       // state of the live table component).
       {name: 'pdfHash', value: window.location.hash.substring(1)}
     );
+    // If the PDF export is triggered on a document revision then we need to make sure that document revision is saved
+    // in the PDF export job context when the job is scheduled.
+    const urlParams = new URLSearchParams(window.location.search);
+    const revision = urlParams.get('rev');
+    if (revision) {
+      data.push({name: 'rev', value: revision});
+    }
     return Promise.resolve(new JobRunner({
       createStatusRequest: function(jobId) {
         resolvePDFExportJobStarted(jobId);
diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/doc/DefaultDocumentAccessBridge.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/doc/DefaultDocumentAccessBridge.java
index 6486d92c2ad..fba7b609337 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/doc/DefaultDocumentAccessBridge.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/doc/DefaultDocumentAccessBridge.java
@@ -179,15 +179,17 @@ public DocumentModelBridge getTranslatedDocumentInstance(DocumentModelBridge doc
     @Override
     public DocumentReference getCurrentDocumentReference()
     {
-        XWikiDocument currentDocument = null;
-        XWikiContext context = getContext();
-        if (context != null) {
-            currentDocument = context.getDoc();
-        }
-
+        DocumentModelBridge currentDocument = getCurrentDocument();
         return currentDocument == null ? null : currentDocument.getDocumentReference();
     }
 
+    @Override
+    public DocumentModelBridge getCurrentDocument()
+    {
+        XWikiContext xcontext = getContext();
+        return xcontext != null ? xcontext.getDoc() : null;
+    }
+
     @Override
     @Deprecated
     public String getDocumentContent(String documentReference) throws Exception
diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/context/XWikiContextContextStore.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/context/XWikiContextContextStore.java
index 1de0b9cf2c9..2ac2f7b6b94 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/context/XWikiContextContextStore.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/context/XWikiContextContextStore.java
@@ -38,6 +38,8 @@
 import javax.inject.Singleton;
 import javax.servlet.http.Cookie;
 
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.slf4j.Logger;
 import org.xwiki.component.annotation.Component;
 import org.xwiki.container.servlet.HttpServletUtils;
@@ -51,6 +53,7 @@
 import com.xpn.xwiki.XWiki;
 import com.xpn.xwiki.XWikiContext;
 import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.doc.DocumentRevisionProvider;
 import com.xpn.xwiki.doc.XWikiDocument;
 import com.xpn.xwiki.user.api.XWikiRightService;
 import com.xpn.xwiki.web.XWikiRequest;
@@ -216,11 +219,26 @@ public class XWikiContextContextStore extends AbstractContextStore
      */
     public static final String SUFFIX_PROP_DOCUMENT_REFERENCE = "reference";
 
+    /**
+     * The suffix of the entry containing the context document revision (version).
+     */
+    public static final String SUFFIX_PROP_DOCUMENT_REVISION = "revision";
+
     /**
      * Name of the entry containing the document reference.
      */
     public static final String PROP_DOCUMENT_REFERENCE = PREFIX_PROP_DOCUMENT + SUFFIX_PROP_DOCUMENT_REFERENCE;
 
+    /**
+     * Name of the entry containing the document revision (version).
+     */
+    public static final String PROP_DOCUMENT_REVISION = PREFIX_PROP_DOCUMENT + SUFFIX_PROP_DOCUMENT_REVISION;
+
+    /**
+     * The XWiki context key used to store the requested document revision (version).
+     */
+    private static final String REV = "rev";
+
     /**
      * The reference of the superadmin user.
      */
@@ -243,6 +261,9 @@ public class XWikiContextContextStore extends AbstractContextStore
     @Inject
     private Logger logger;
 
+    @Inject
+    private DocumentRevisionProvider documentRevisionProvider;
+
     /**
      * Default constructor.
      */
@@ -250,7 +271,7 @@ public XWikiContextContextStore()
     {
         super(PROP_WIKI, PROP_USER, PROP_LOCALE, PROP_ACTION, PROP_REQUEST_BASE, PROP_REQUEST_URL,
             PROP_REQUEST_PARAMETERS, PROP_REQUEST_HEADERS, PROP_REQUEST_COOKIES, PROP_REQUEST_REMOTE_ADDR,
-            PROP_REQUEST_WIKI, PROP_DOCUMENT_REFERENCE);
+            PROP_REQUEST_WIKI, PROP_DOCUMENT_REFERENCE, PROP_DOCUMENT_REVISION);
     }
 
     @Override
@@ -266,21 +287,28 @@ public void save(Map<String, Serializable> contextStore, Collection<String> entr
             save(contextStore, PROP_LOCALE, xcontext.getLocale(), entries);
             save(contextStore, PROP_ACTION, xcontext.getAction(), entries);
 
-            save(contextStore, PREFIX_PROP_DOCUMENT, xcontext.getDoc(), entries);
+            saveDocument(contextStore, PREFIX_PROP_DOCUMENT, xcontext, entries);
 
             save(contextStore, PREFIX_PROP_REQUEST, xcontext.getRequest(), entries, xcontext);
         }
     }
 
-    private void save(Map<String, Serializable> contextStore, String prefix, XWikiDocument document,
+    private void saveDocument(Map<String, Serializable> contextStore, String prefix, XWikiContext xcontext,
         Collection<String> entries)
     {
+        XWikiDocument document = xcontext.getDoc();
         if (document != null) {
             save((key, subkey) -> {
                 switch (subkey) {
                     case SUFFIX_PROP_DOCUMENT_REFERENCE:
                         contextStore.put(key, document.getDocumentReferenceWithLocale());
                         break;
+                    case SUFFIX_PROP_DOCUMENT_REVISION:
+                        // Save the document revision only if it matches the revision that was requested explicitly.
+                        if (Objects.equals(document.getVersion(), xcontext.get(REV))) {
+                            contextStore.put(key, document.getVersion());
+                        }
+                        break;
 
                     // TODO: support other properties ?
 
@@ -525,6 +553,10 @@ private XWikiDocument getSecureDocument(DocumentReference secureDocumentReferenc
 
     private void restoreDocument(String storedWikiId, Map<String, Serializable> contextStore, XWikiContext xcontext)
     {
+        if (contextStore.containsKey(PROP_DOCUMENT_REVISION)) {
+            xcontext.put(REV, contextStore.get(PROP_DOCUMENT_REVISION));
+        }
+
         if (contextStore.containsKey(PROP_DOCUMENT_REFERENCE)) {
             DocumentReference document = (DocumentReference) contextStore.get(PROP_DOCUMENT_REFERENCE);
             restoreDocument(document, xcontext);
@@ -558,9 +590,18 @@ private void restoreDocument(DocumentReference documentReference, XWikiContext x
             return;
         }
 
+        String revision = (String) xcontext.get(REV);
+        if (StringUtils.isNotEmpty(revision)) {
+            try {
+                document = this.documentRevisionProvider.getRevision(document, revision);
+            } catch (XWikiException e) {
+                this.logger.warn("Failed to load revision [{}] of document [{}]. Root cause is [{}].", revision,
+                    documentReference, ExceptionUtils.getRootCauseMessage(e));
+            }
+        }
+
         // TODO: customize the document with what's in the contextStore if any
 
         xcontext.setDoc(document);
     }
-
 }
diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/doc/DefaultDocumentAccessBridgeTest.java b/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/doc/DefaultDocumentAccessBridgeTest.java
index 3375b85cbde..7070b999ead 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/doc/DefaultDocumentAccessBridgeTest.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/doc/DefaultDocumentAccessBridgeTest.java
@@ -35,6 +35,7 @@
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
@@ -87,4 +88,16 @@ void setAttachmentContent(boolean useStream, MockitoOldcore oldcore) throws Exce
         assertArrayEquals(attachmentContent,
             IOUtils.toByteArray(attachment.getAttachmentContent(oldcore.getXWikiContext()).getContentInputStream()));
     }
+
+    @Test
+    void getCurrentDocumentReference(MockitoOldcore oldcore)
+    {
+        assertNull(this.documentAccessBridge.getCurrentDocumentReference());
+
+        DocumentReference documentReference = new DocumentReference("test", "Some", "Page");
+        XWikiDocument document = new XWikiDocument(documentReference);
+        oldcore.getXWikiContext().setDoc(document);
+
+        assertEquals(documentReference, this.documentAccessBridge.getCurrentDocumentReference());
+    }
 }
diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/internal/context/XWikiContextContextStoreTest.java b/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/internal/context/XWikiContextContextStoreTest.java
index 5126dd805f2..2c89a0fb6c8 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/internal/context/XWikiContextContextStoreTest.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/internal/context/XWikiContextContextStoreTest.java
@@ -27,6 +27,7 @@
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 import javax.servlet.http.Cookie;
@@ -44,6 +45,7 @@
 import org.xwiki.wiki.manager.WikiManagerException;
 
 import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.doc.DocumentRevisionProvider;
 import com.xpn.xwiki.doc.XWikiDocument;
 import com.xpn.xwiki.test.MockitoOldcore;
 import com.xpn.xwiki.test.junit5.mockito.InjectMockitoOldcore;
@@ -88,6 +90,9 @@ class XWikiContextContextStoreTest
     @InjectMockComponents
     private XWikiContextContextStore store;
 
+    @MockComponent
+    private DocumentRevisionProvider documentRevisionProvider;
+
     private WikiDescriptor descriptor;
 
     private URL wikiURL;
@@ -326,4 +331,40 @@ void restoreAuthor() throws XWikiException
 
         assertNull(this.oldcore.getXWikiContext().getUserReference());
     }
+
+    @Test
+    void saveAndRestoreDocumentRevision() throws Exception
+    {
+        // Save
+
+        DocumentReference documentReference = new DocumentReference("test", "Some", "Page");
+        DocumentReference documentReferenceWithLocale = new DocumentReference(documentReference, Locale.FRENCH);
+        XWikiDocument documentRevision = new XWikiDocument(documentReferenceWithLocale);
+        documentRevision.setLocale(Locale.FRENCH);
+        documentRevision.setVersion("2.5");
+        this.oldcore.getXWikiContext().setDoc(documentRevision);
+        this.oldcore.getXWikiContext().put("rev", documentRevision.getVersion());
+
+        Map<String, Serializable> contextStore = new HashMap<>();
+        this.store.save(contextStore, Arrays.asList(XWikiContextContextStore.PROP_DOCUMENT_REFERENCE,
+            XWikiContextContextStore.PROP_DOCUMENT_REVISION));
+
+        assertEquals(documentReferenceWithLocale, contextStore.get(XWikiContextContextStore.PROP_DOCUMENT_REFERENCE));
+        assertEquals("2.5", contextStore.get(XWikiContextContextStore.PROP_DOCUMENT_REVISION));
+
+        // Restore
+
+        this.oldcore.getXWikiContext().setDoc(null);
+        this.oldcore.getXWikiContext().remove("rev");
+
+        XWikiDocument document = new XWikiDocument(documentReferenceWithLocale);
+        when(this.oldcore.getSpyXWiki().getDocument(documentReferenceWithLocale, this.oldcore.getXWikiContext()))
+            .thenReturn(document);
+        when(this.documentRevisionProvider.getRevision(document, "2.5")).thenReturn(documentRevision);
+
+        this.store.restore(contextStore);
+
+        assertEquals("2.5", this.oldcore.getXWikiContext().get("rev"));
+        assertEquals(documentRevision, this.oldcore.getXWikiContext().getDoc());
+    }
 }
-- 
GitLab