/* * 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.test.ui; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.net.URI; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriBuilder; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.methods.DeleteMethod; import org.apache.commons.httpclient.methods.EntityEnclosingMethod; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.InputStreamRequestEntity; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.PutMethod; import org.apache.commons.httpclient.methods.RequestEntity; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.junit.Assert; import org.openqa.selenium.By; import org.openqa.selenium.Cookie; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedCondition; import org.xwiki.component.manager.ComponentManager; import org.xwiki.model.EntityType; import org.xwiki.model.reference.AttachmentReference; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.EntityReference; import org.xwiki.model.reference.EntityReferenceResolver; import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.model.reference.LocalDocumentReference; import org.xwiki.rest.model.jaxb.Page; import org.xwiki.rest.model.jaxb.Property; import org.xwiki.rest.model.jaxb.Xwiki; import org.xwiki.rest.resources.attachments.AttachmentResource; import org.xwiki.rest.resources.classes.ClassPropertyResource; import org.xwiki.rest.resources.objects.ObjectPropertyResource; import org.xwiki.rest.resources.objects.ObjectResource; import org.xwiki.rest.resources.objects.ObjectsResource; import org.xwiki.rest.resources.pages.PageResource; import org.xwiki.rest.resources.pages.PageTranslationResource; import org.xwiki.test.integration.XWikiExecutor; import org.xwiki.test.ui.po.ViewPage; import org.xwiki.test.ui.po.editor.ClassEditPage; import org.xwiki.test.ui.po.editor.ObjectEditPage; /** * Helper methods for testing, not related to a specific Page Object. Also made available to tests classes. * * @version $Id: a806f31eb5ba4c08c29829271d99c959ba7bb336 $ * @since 3.2M3 */ public class TestUtils { /** * @since 5.0M2 */ public static final UsernamePasswordCredentials ADMIN_CREDENTIALS = new UsernamePasswordCredentials("Admin", "admin"); /** * @since 5.1M1 */ public static final UsernamePasswordCredentials SUPER_ADMIN_CREDENTIALS = new UsernamePasswordCredentials("superadmin", "pass"); /** * @since 5.0M2 * @deprecated since 7.3M1, use {@link #getBaseURL()} instead */ @Deprecated public static final String BASE_URL = XWikiExecutor.URL + ":" + XWikiExecutor.DEFAULT_PORT + "/xwiki/"; /** * @since 5.0M2 * @deprecated since 7.3M1, use {@link #getBaseBinURL()} instead */ @Deprecated public static final String BASE_BIN_URL = BASE_URL + "bin/"; /** * @since 5.0M2 * @deprecated since 7.3M1, use {@link #getBaseURL()} instead */ @Deprecated public static final String BASE_REST_URL = BASE_URL + "rest/"; /** * @since 7.3M1 */ public static final int[] STATUS_OK_NOT_FOUND = new int[] { Status.OK.getStatusCode(), Status.NOT_FOUND.getStatusCode() }; /** * @since 7.3M1 */ public static final int[] STATUS_OK = new int[] { Status.OK.getStatusCode() }; /** * @since 7.3M1 */ public static final int[] STATUS_NO_CONTENT = new int[] { Status.NO_CONTENT.getStatusCode() }; /** * @since 8.3RC1 */ public static final int[] STATUS_NO_CONTENT_NOT_FOUND = new int[] { Status.NO_CONTENT.getStatusCode(), Status.NOT_FOUND.getStatusCode() }; /** * @since 7.3M1 */ public static final int[] STATUS_CREATED_ACCEPTED = new int[] { Status.CREATED.getStatusCode(), Status.ACCEPTED.getStatusCode() }; /** * @since 7.3M1 */ public static final int[] STATUS_CREATED = new int[] { Status.CREATED.getStatusCode() }; private static PersistentTestContext context; private static ComponentManager componentManager; private static EntityReferenceResolver<String> relativeReferenceResolver; private static EntityReferenceSerializer<String> referenceSerializer; private static EntityReferenceResolver<String> referenceResolver; /** * Used to convert Java object into its REST XML representation. */ private static Marshaller marshaller; /** * Used to convert REST request XML result into its Java representation. */ private static Unmarshaller unmarshaller; /** Cached secret token. TODO cache for each user. */ private String secretToken = null; private HttpClient httpClient; /** * @since 8.0M1 */ private List<XWikiExecutor> executors; /** * @since 7.3M1 */ private int currentExecutorIndex; /** * @since 7.3M1 */ private String currentWiki = "xwiki"; private RestTestUtils rest; public TestUtils() { this.httpClient = new HttpClient(); this.httpClient.getState().setCredentials(AuthScope.ANY, SUPER_ADMIN_CREDENTIALS); this.httpClient.getParams().setAuthenticationPreemptive(true); this.rest = new RestTestUtils(this); } /** * @since 8.0M1 */ public XWikiExecutor getCurrentExecutor() { return this.executors != null && this.executors.size() > this.currentExecutorIndex ? this.executors.get(this.currentExecutorIndex) : null; } /** * @since 8.0M1 */ public void switchExecutor(int index) { this.currentExecutorIndex = index; } /** * @since 8.0M1 */ public void setExecutors(List<XWikiExecutor> executors) { this.executors = executors; } /** Used so that AllTests can set the persistent test context. */ public static void setContext(PersistentTestContext context) { TestUtils.context = context; } public static void initializeComponent(ComponentManager componentManager) throws Exception { TestUtils.componentManager = componentManager; TestUtils.relativeReferenceResolver = TestUtils.componentManager.getInstance(EntityReferenceResolver.TYPE_STRING, "relative"); TestUtils.referenceResolver = TestUtils.componentManager.getInstance(EntityReferenceResolver.TYPE_STRING); TestUtils.referenceSerializer = TestUtils.componentManager.getInstance(EntityReferenceSerializer.TYPE_STRING); } public XWikiWebDriver getDriver() { return context.getDriver(); } public Session getSession() { return this.new Session(getDriver().manage().getCookies(), getSecretToken()); } public void setSession(Session session) { WebDriver.Options options = getDriver().manage(); options.deleteAllCookies(); if (session != null) { for (Cookie cookie : session.getCookies()) { options.addCookie(cookie); } } if (session != null && !StringUtils.isEmpty(session.getSecretToken())) { this.secretToken = session.getSecretToken(); } else { recacheSecretToken(); } } /** * @since 7.0RC1 */ public void setDefaultCredentials(String username, String password) { setDefaultCredentials(new UsernamePasswordCredentials(username, password)); } /** * @since 7.0RC1 */ public UsernamePasswordCredentials setDefaultCredentials(UsernamePasswordCredentials defaultCredentials) { UsernamePasswordCredentials currentCredentials = getDefaultCredentials(); this.httpClient.getState().setCredentials(AuthScope.ANY, defaultCredentials); return currentCredentials; } public UsernamePasswordCredentials getDefaultCredentials() { return (UsernamePasswordCredentials) this.httpClient.getState().getCredentials(AuthScope.ANY); } public void loginAsSuperAdmin() { login(SUPER_ADMIN_CREDENTIALS.getUserName(), SUPER_ADMIN_CREDENTIALS.getPassword()); } public void loginAsSuperAdminAndGotoPage(String pageURL) { loginAndGotoPage(SUPER_ADMIN_CREDENTIALS.getUserName(), SUPER_ADMIN_CREDENTIALS.getPassword(), pageURL); } public void loginAsAdmin() { login(ADMIN_CREDENTIALS.getUserName(), ADMIN_CREDENTIALS.getPassword()); } public void loginAsAdminAndGotoPage(String pageURL) { loginAndGotoPage(ADMIN_CREDENTIALS.getUserName(), ADMIN_CREDENTIALS.getPassword(), pageURL); } public void login(String username, String password) { loginAndGotoPage(username, password, null); } public void loginAndGotoPage(String username, String password, String pageURL) { if (!username.equals(getLoggedInUserName())) { // Log in and direct to a non existent page so that it loads very fast and we don't incur the time cost of // going to the home page for example. // Also recache the CSRF token getDriver().get(getURLToLoginAndGotoPage(username, password, getURL("XWiki", "Register", "register"))); recacheSecretTokenWhenOnRegisterPage(); if (pageURL != null) { // Go to the page asked getDriver().get(pageURL); } else { getDriver().get(getURLToNonExistentPage()); } setDefaultCredentials(username, password); } } /** * Consider using setSession(null) because it will drop the cookies which is faster than invoking a logout action. */ public String getURLToLogout() { return getURL("XWiki", "XWikiLogin", "logout"); } public String getURLToLoginAsAdmin() { return getURLToLoginAs(ADMIN_CREDENTIALS.getUserName(), ADMIN_CREDENTIALS.getPassword()); } public String getURLToLoginAsSuperAdmin() { return getURLToLoginAs(SUPER_ADMIN_CREDENTIALS.getUserName(), SUPER_ADMIN_CREDENTIALS.getPassword()); } public String getURLToLoginAs(final String username, final String password) { return getURLToLoginAndGotoPage(username, password, null); } /** * @param pageURL the URL of the page to go to after logging in. * @return URL to accomplish login and goto. */ public String getURLToLoginAsAdminAndGotoPage(final String pageURL) { return getURLToLoginAndGotoPage(ADMIN_CREDENTIALS.getUserName(), ADMIN_CREDENTIALS.getPassword(), pageURL); } /** * @param pageURL the URL of the page to go to after logging in. * @return URL to accomplish login and goto. */ public String getURLToLoginAsSuperAdminAndGotoPage(final String pageURL) { return getURLToLoginAndGotoPage(SUPER_ADMIN_CREDENTIALS.getUserName(), SUPER_ADMIN_CREDENTIALS.getPassword(), pageURL); } /** * @param username the name of the user to log in as. * @param password the password for the user to log in. * @param pageURL the URL of the page to go to after logging in. * @return URL to accomplish login and goto. */ public String getURLToLoginAndGotoPage(final String username, final String password, final String pageURL) { Map<String, String> parameters = new HashMap<String, String>() { { put("j_username", username); put("j_password", password); if (pageURL != null && pageURL.length() > 0) { put("xredirect", pageURL); } } }; return getURL("XWiki", "XWikiLogin", "loginsubmit", parameters); } /** * @return URL to a non existent page that loads very fast (we are using plain mode so that we don't even have to * display the skin ;)) */ public String getURLToNonExistentPage() { return getURL("NonExistentSpace", "NonExistentPage", "view", "xpage=plain"); } /** * After successful completion of this function, you are guaranteed to be logged in as the given user and on the * page passed in pageURL. */ public void assertOnPage(final String pageURL) { final String pageURI = pageURL.replaceAll("\\?.*", ""); getDriver().waitUntilCondition(new ExpectedCondition<Boolean>() { @Override public Boolean apply(WebDriver driver) { return getDriver().getCurrentUrl().contains(pageURI); } }); } public String getLoggedInUserName() { By userAvatarInDrawer = By.id("tmUser"); if (!getDriver().hasElementWithoutWaiting(userAvatarInDrawer)) { // Guest return null; } WebElement element = getDriver().findElementWithoutWaiting(userAvatarInDrawer); String href = element.getAttribute("href"); String loggedInUserName = href.substring(href.lastIndexOf("/") + 1); // Return return loggedInUserName; } public void createUserAndLogin(final String username, final String password, Object... properties) { createUserAndLoginWithRedirect(username, password, getURLToNonExistentPage(), properties); } public void createUserAndLoginWithRedirect(final String username, final String password, String url, Object... properties) { createUser(username, password, getURLToLoginAndGotoPage(username, password, url), properties); setDefaultCredentials(username, password); } public void createUser(final String username, final String password, String redirectURL, Object... properties) { Map<String, String> parameters = new HashMap<String, String>(); parameters.put("register", "1"); parameters.put("xwikiname", username); parameters.put("register_password", password); parameters.put("register2_password", password); parameters.put("register_email", ""); parameters.put("xredirect", redirectURL); parameters.put("form_token", getSecretToken()); getDriver().get(getURL("XWiki", "Register", "register", parameters)); recacheSecretToken(); if (properties.length > 0) { updateObject("XWiki", username, "XWiki.XWikiUsers", 0, properties); } } public ViewPage gotoPage(String space, String page) { gotoPage(space, page, "view"); return new ViewPage(); } /** * @since 7.2M2 */ public ViewPage gotoPage(EntityReference reference) { gotoPage(reference, "view"); return new ViewPage(); } public void gotoPage(String space, String page, String action) { gotoPage(space, page, action, ""); } /** * @since 7.2M2 */ public void gotoPage(EntityReference reference, String action) { gotoPage(reference, action, ""); } /** * @since 3.5M1 */ public void gotoPage(String space, String page, String action, Object... queryParameters) { gotoPage(space, page, action, toQueryString(queryParameters)); } public void gotoPage(String space, String page, String action, Map<String, ?> queryParameters) { gotoPage(Collections.singletonList(space), page, action, queryParameters); } /** * @since 7.2M2 */ public void gotoPage(List<String> spaces, String page, String action, Map<String, ?> queryParameters) { gotoPage(spaces, page, action, toQueryString(queryParameters)); } /** * @since 7.2M2 */ public void gotoPage(EntityReference reference, String action, Map<String, ?> queryParameters) { gotoPage(reference, action, toQueryString(queryParameters)); } public void gotoPage(String space, String page, String action, String queryString) { gotoPage(Collections.singletonList(space), page, action, queryString); } /** * @since 7.2M2 */ public void gotoPage(List<String> spaces, String page, String action, String queryString) { gotoPage(getURL(spaces, page, action, queryString)); } /** * @since 7.2M2 */ public void gotoPage(EntityReference reference, String action, String queryString) { gotoPage(getURL(reference, action, queryString)); // Update current wiki EntityReference wikiReference = reference.extractReference(EntityType.WIKI); if (wikiReference != null) { this.currentWiki = wikiReference.getName(); } } public void gotoPage(String url) { // Only navigate if the current URL is different from the one to go to, in order to improve performances. if (!getDriver().getCurrentUrl().equals(url)) { getDriver().get(url); } } public String getURLToDeletePage(String space, String page) { return getURL(space, page, "delete", "confirm=1"); } /** * @since 7.2M2 */ public String getURLToDeletePage(EntityReference reference) { return getURL(reference, "delete", "confirm=1"); } /** * @param space the name of the space to delete * @return the URL that can be used to delete the specified pace * @since 4.5 */ public String getURLToDeleteSpace(String space) { return getURL(space, "WebHome", "deletespace", "confirm=1&async=false&affectChidlren=on"); } public ViewPage createPage(String space, String page, String content, String title) { return createPage(Collections.singletonList(space), page, content, title); } /** * @since 7.2M2 */ public ViewPage createPage(EntityReference reference, String content, String title) { return createPage(reference, content, title, null); } /** * @since 7.2M2 */ public ViewPage createPage(List<String> spaces, String page, String content, String title) { return createPage(spaces, page, content, title, null); } public ViewPage createPage(String space, String page, String content, String title, String syntaxId) { return createPage(Collections.singletonList(space), page, content, title, syntaxId); } /** * @since 7.2M2 */ public ViewPage createPage(EntityReference reference, String content, String title, String syntaxId) { return createPage(reference, content, title, syntaxId, null); } /** * @since 7.2M2 */ public ViewPage createPage(List<String> spaces, String page, String content, String title, String syntaxId) { return createPage(spaces, page, content, title, syntaxId, null); } public ViewPage createPage(String space, String page, String content, String title, String syntaxId, String parentFullPageName) { return createPage(Collections.singletonList(space), page, content, title, syntaxId, parentFullPageName); } /** * @since 7.2M2 */ public ViewPage createPage(List<String> spaces, String page, String content, String title, String syntaxId, String parentFullPageName) { Map<String, String> queryMap = new HashMap<String, String>(); if (content != null) { queryMap.put("content", content); } if (title != null) { queryMap.put("title", title); } if (syntaxId != null) { queryMap.put("syntaxId", syntaxId); } if (parentFullPageName != null) { queryMap.put("parent", parentFullPageName); } gotoPage(spaces, page, "save", queryMap); return new ViewPage(); } /** * @since 7.2M2 */ public ViewPage createPage(EntityReference reference, String content, String title, String syntaxId, String parentFullPageName) { Map<String, String> queryMap = new HashMap<>(); if (content != null) { queryMap.put("content", content); } if (title != null) { queryMap.put("title", title); } if (syntaxId != null) { queryMap.put("syntaxId", syntaxId); } if (parentFullPageName != null) { queryMap.put("parent", parentFullPageName); } gotoPage(reference, "save", queryMap); return new ViewPage(); } /** * @since 5.1M2 */ public ViewPage createPageWithAttachment(String space, String page, String content, String title, String syntaxId, String parentFullPageName, String attachmentName, InputStream attachmentData) throws Exception { return createPageWithAttachment(space, page, content, title, syntaxId, parentFullPageName, attachmentName, attachmentData, null); } /** * @since 5.1M2 */ public ViewPage createPageWithAttachment(String space, String page, String content, String title, String syntaxId, String parentFullPageName, String attachmentName, InputStream attachmentData, UsernamePasswordCredentials credentials) throws Exception { return createPageWithAttachment(Collections.singletonList(space), page, content, title, syntaxId, parentFullPageName, attachmentName, attachmentData, credentials); } /** * @since 7.2M2 */ public ViewPage createPageWithAttachment(List<String> spaces, String page, String content, String title, String syntaxId, String parentFullPageName, String attachmentName, InputStream attachmentData, UsernamePasswordCredentials credentials) throws Exception { ViewPage vp = createPage(spaces, page, content, title, syntaxId, parentFullPageName); attachFile(spaces, page, attachmentName, attachmentData, false, credentials); return vp; } /** * @since 5.1M2 */ public ViewPage createPageWithAttachment(String space, String page, String content, String title, String attachmentName, InputStream attachmentData) throws Exception { return createPageWithAttachment(space, page, content, title, null, null, attachmentName, attachmentData); } /** * @since 5.1M2 */ public ViewPage createPageWithAttachment(String space, String page, String content, String title, String attachmentName, InputStream attachmentData, UsernamePasswordCredentials credentials) throws Exception { ViewPage vp = createPage(space, page, content, title); attachFile(space, page, attachmentName, attachmentData, false, credentials); return vp; } public void deletePage(String space, String page) { getDriver().get(getURLToDeletePage(space, page)); } /** * @since 7.2M2 */ public void deletePage(EntityReference reference) { getDriver().get(getURLToDeletePage(reference)); } /** * @since 7.2M2 */ public EntityReference resolveDocumentReference(String referenceAsString) { return referenceResolver.resolve(referenceAsString, EntityType.DOCUMENT); } /** * @since 7.2M3 */ public EntityReference resolveSpaceReference(String referenceAsString) { return referenceResolver.resolve(referenceAsString, EntityType.SPACE); } /** * @since 7.2RC1 */ public String serializeReference(EntityReference reference) { return referenceSerializer.serialize(reference); } /** * Accesses the URL to delete the specified space. * * @param space the name of the space to delete * @since 4.5 */ public void deleteSpace(String space) { getDriver().get(getURLToDeleteSpace(space)); } public boolean pageExists(String space, String page) throws Exception { return rest().exists(new LocalDocumentReference(space, page)); } /** * @since 7.2M2 */ public boolean pageExists(List<String> spaces, String page) throws Exception { return rest().exists(new LocalDocumentReference(spaces, page)); } /** * Get the URL to view a page. * * @param space the space in which the page resides. * @param page the name of the page. */ public String getURL(String space, String page) { return getURL(space, page, "view"); } /** * Get the URL of an action on a page. * * @param space the space in which the page resides. * @param page the name of the page. * @param action the action to do on the page. */ public String getURL(String space, String page, String action) { return getURL(space, page, action, ""); } /** * Get the URL of an action on a page with a specified query string. * * @param space the space in which the page resides. * @param page the name of the page. * @param action the action to do on the page. * @param queryString the query string to pass in the URL. */ public String getURL(String space, String page, String action, String queryString) { return getURL(action, new String[] { space, page }, queryString); } /** * @since 7.3M1 */ public String getURL(List<String> spaces, String page) { return getURL(spaces, page, "view", ""); } /** * @since 7.2M2 */ public String getURL(List<String> spaces, String page, String action, String queryString) { List<String> path = new ArrayList<>(spaces); path.add(page); return getURL(action, path.toArray(new String[] {}), queryString); } /** * @since 7.2M2 */ public String getURL(EntityReference reference, String action, String queryString) { return getURL(action, extractListFromReference(reference).toArray(new String[] {}), queryString); } /** * @since 7.2M2 */ public String getURLFragment(EntityReference reference) { return StringUtils.join(extractListFromReference(reference), "/"); } private List<String> extractListFromReference(EntityReference reference) { List<String> path = new ArrayList<>(); // Add the spaces EntityReference spaceReference = reference.extractReference(EntityType.SPACE); EntityReference wikiReference = reference.extractReference(EntityType.WIKI); for (EntityReference singleReference : spaceReference.removeParent(wikiReference).getReversedReferenceChain()) { path.add(singleReference.getName()); } if (reference.getType() == EntityType.DOCUMENT) { path.add(reference.getName()); } return path; } /** * @since 7.3M1 */ public String getCurrentWiki() { return this.currentWiki; } /** * @since 7.3M1 */ public String getBaseURL() { return XWikiExecutor.URL + ":" + (getCurrentExecutor() != null ? getCurrentExecutor().getPort() : XWikiExecutor.DEFAULT_PORT) + "/xwiki/"; } /** * @since 7.3M1 */ public String getBaseBinURL() { return getBaseURL() + "bin/"; } /** * @since 7.2M1 */ public String getURL(String action, String[] path, String queryString) { StringBuilder builder = new StringBuilder(getBaseBinURL()); if (!StringUtils.isEmpty(action)) { builder.append(action).append('/'); } List<String> escapedPath = new ArrayList<>(); for (String element : path) { escapedPath.add(escapeURL(element)); } builder.append(StringUtils.join(escapedPath, '/')); boolean needToAddSecretToken = !Arrays.asList("view", "register", "download").contains(action); if (needToAddSecretToken || !StringUtils.isEmpty(queryString)) { builder.append('?'); } if (needToAddSecretToken) { addQueryStringEntry(builder, "form_token", getSecretToken()); builder.append('&'); } if (!StringUtils.isEmpty(queryString)) { builder.append(queryString); } return builder.toString(); } /** * Get the URL of an action on a page with specified parameters. If you need to pass multiple parameters with the * same key, this function will not work. * * @param space the space in which the page resides. * @param page the name of the page. * @param action the action to do on the page. * @param queryParameters the parameters to pass in the URL, these will be automatically URL encoded. */ public String getURL(String space, String page, String action, Map<String, ?> queryParameters) { return getURL(space, page, action, toQueryString(queryParameters)); } /** * @param space the name of the space that contains the page with the specified attachment * @param page the name of the page that holds the attachment * @param attachment the attachment name * @param action the action to perform on the attachment * @param queryString the URL query string * @return the URL that performs the specified action on the specified attachment */ public String getAttachmentURL(String space, String page, String attachment, String action, String queryString) { return getURL(action, new String[] { space, page, attachment }, queryString); } /** * @param space the name of the space that contains the page with the specified attachment * @param page the name of the page that holds the attachment * @param attachment the attachment name * @param action the action to perform on the attachment * @return the URL that performs the specified action on the specified attachment */ public String getAttachmentURL(String space, String page, String attachment, String action) { return getAttachmentURL(space, page, attachment, action, ""); } /** * @param space the name of the space that contains the page with the specified attachment * @param page the name of the page that holds the attachment * @param attachment the attachment name * @return the URL to download the specified attachment */ public String getAttachmentURL(String space, String page, String attachment) { return getAttachmentURL(space, page, attachment, "download"); } /** * (Re)-cache the secret token used for CSRF protection. A user with edit rights on Main.WebHome must be logged in. * This method must be called before {@link #getSecretToken()} is called and after each re-login. * * @see #getSecretToken() */ public void recacheSecretToken() { // Save the current URL to be able to get back after we cache the secret token. We're not using the browser's // Back button because if the current page is the result of a POST request then by going back we are re-sending // the POST data which can have unexpected results. Moreover, some browsers pop up a modal confirmation box // which blocks the test. String previousURL = getDriver().getCurrentUrl(); // Go to the registration page because the registration form uses secret token. gotoPage(getCurrentWiki(), "Register", "register"); recacheSecretTokenWhenOnRegisterPage(); // Return to the previous page. getDriver().get(previousURL); } private void recacheSecretTokenWhenOnRegisterPage() { try { WebElement tokenInput = getDriver().findElement(By.xpath("//input[@name='form_token']")); this.secretToken = tokenInput.getAttribute("value"); } catch (NoSuchElementException exception) { // Something is really wrong if this happens. System.out.println("Warning: Failed to cache anti-CSRF secret token, some tests might fail!"); exception.printStackTrace(); } } /** * Get the secret token used for CSRF protection. Remember to call {@link #recacheSecretToken()} first. * * @return anti-CSRF secret token, or empty string if the token was not cached * @see #recacheSecretToken() */ public String getSecretToken() { if (this.secretToken == null) { System.out.println("Warning: No cached anti-CSRF token found. " + "Make sure to call recacheSecretToken() before getSecretToken(), otherwise this test might fail."); return ""; } return this.secretToken; } /** * This class represents all cookies stored in the browser. Use with getSession() and setSession() */ public class Session { private final Set<Cookie> cookies; private final String secretToken; private Session(final Set<Cookie> cookies, final String secretToken) { this.cookies = Collections.unmodifiableSet(new HashSet<Cookie>() { { addAll(cookies); } }); this.secretToken = secretToken; } private Set<Cookie> getCookies() { return this.cookies; } private String getSecretToken() { return this.secretToken; } } public boolean isInWYSIWYGEditMode() { return getDriver().findElements(By.xpath("//div[@id='editcolumn' and contains(@class, 'editor-wysiwyg')]")) .size() > 0; } public boolean isInWikiEditMode() { return getDriver().findElements(By.xpath("//div[@id='editcolumn' and contains(@class, 'editor-wiki')]")) .size() > 0; } public boolean isInViewMode() { return !getDriver().hasElementWithoutWaiting(By.id("editMeta")); } public boolean isInSourceViewMode() { return getDriver().findElements(By.xpath("//textarea[@class = 'wiki-code']")).size() > 0; } public boolean isInInlineEditMode() { String currentURL = getDriver().getCurrentUrl(); // Keep checking the deprecated inline action for backward compatibility. return currentURL.contains("editor=inline") || currentURL.contains("/inline/"); } public boolean isInRightsEditMode() { return getDriver().getCurrentUrl().contains("editor=rights"); } public boolean isInObjectEditMode() { return getDriver().getCurrentUrl().contains("editor=object"); } public boolean isInClassEditMode() { return getDriver().getCurrentUrl().contains("editor=class"); } public boolean isInDeleteMode() { return getDriver().getCurrentUrl().contains("/delete/"); } public boolean isInRenameMode() { return getDriver().getCurrentUrl().contains("xpage=rename"); } public boolean isInCreateMode() { return getDriver().getCurrentUrl().contains("/create/"); } public boolean isInAdminMode() { return getDriver().getCurrentUrl().contains("/admin/"); } /** * Forces the current user to be the Guest user by clearing all coookies. */ public void forceGuestUser() { setSession(null); } public void addObject(String space, String page, String className, Object... properties) { gotoPage(space, page, "objectadd", toQueryParameters(className, null, properties)); } /** * @since 7.2RC1 */ public void addObject(EntityReference reference, String className, Object... properties) { gotoPage(reference, "objectadd", toQueryParameters(className, null, properties)); } /** * @since 7.3M2 */ public void addObject(EntityReference reference, String className, Map<String, ?> properties) { gotoPage(reference, "objectadd", toQueryParameters(className, null, properties)); } public void addObject(String space, String page, String className, Map<String, ?> properties) { gotoPage(space, page, "objectadd", toQueryParameters(className, null, properties)); } public void deleteObject(String space, String page, String className, int objectNumber) throws Exception { TestUtils.assertStatusCodes( rest().executeDelete(ObjectResource.class, getCurrentWiki(), space, page, className, objectNumber), true, STATUS_NO_CONTENT_NOT_FOUND); } public void updateObject(String space, String page, String className, int objectNumber, Map<String, ?> properties) { gotoPage(space, page, "save", toQueryParameters(className, objectNumber, properties)); } public void updateObject(String space, String page, String className, int objectNumber, Object... properties) { updateObject(Collections.singletonList(space), page, className, objectNumber, properties); } /** * @since 8.3RC1 */ public void updateObject(List<String> spaces, String page, String className, int objectNumber, Object... properties) { // TODO: would be even quicker using REST Map<String, Object> queryParameters = (Map<String, Object>) toQueryParameters(className, objectNumber, properties); // Append the updateOrCreate objectPolicy since we always want this in our tests. queryParameters.put("objectPolicy", "updateOrCreate"); gotoPage(spaces, page, "save", queryParameters); } public void addClassProperty(String space, String page, String propertyName, String propertyType) { gotoPage(space, page, "propadd", "propname", propertyName, "proptype", propertyType); } /** * @since 3.5M1 */ public String toQueryString(Object... queryParameters) { return toQueryString(toQueryParameters(queryParameters)); } /** * @since 3.5M1 */ public String toQueryString(Map<String, ?> queryParameters) { StringBuilder builder = new StringBuilder(); for (Map.Entry<String, ?> entry : queryParameters.entrySet()) { addQueryStringEntry(builder, entry.getKey(), entry.getValue()); builder.append('&'); } return builder.toString(); } /** * @since 3.2M1 */ public void addQueryStringEntry(StringBuilder builder, String key, Object value) { if (value != null) { if (value instanceof Iterable) { for (Object element : (Iterable<?>) value) { addQueryStringEntry(builder, key, element.toString()); builder.append('&'); } } else { addQueryStringEntry(builder, key, value.toString()); } } else { addQueryStringEntry(builder, key, (String) null); } } /** * @since 3.2M1 */ public void addQueryStringEntry(StringBuilder builder, String key, String value) { builder.append(escapeURL(key)); if (value != null) { builder.append('='); builder.append(escapeURL(value)); } } /** * @since 3.5M1 */ public Map<String, ?> toQueryParameters(Object... properties) { return toQueryParameters(null, null, properties); } public Map<String, ?> toQueryParameters(String className, Integer objectNumber, Object... properties) { Map<String, Object> queryParameters = new HashMap<String, Object>(); queryParameters.put("classname", className); for (int i = 0; i < properties.length; i += 2) { int nextIndex = i + 1; queryParameters.put(toQueryParameterKey(className, objectNumber, (String) properties[i]), nextIndex < properties.length ? properties[nextIndex] : null); } return queryParameters; } public Map<String, ?> toQueryParameters(String className, Integer objectNumber, Map<String, ?> properties) { Map<String, Object> queryParameters = new HashMap<String, Object>(); if (className != null) { queryParameters.put("classname", className); } for (Map.Entry<String, ?> entry : properties.entrySet()) { queryParameters.put(toQueryParameterKey(className, objectNumber, entry.getKey()), entry.getValue()); } return queryParameters; } public String toQueryParameterKey(String className, Integer objectNumber, String key) { if (className == null) { return key; } else { StringBuilder keyBuilder = new StringBuilder(className); keyBuilder.append('_'); if (objectNumber != null) { keyBuilder.append(objectNumber); keyBuilder.append('_'); } keyBuilder.append(key); return keyBuilder.toString(); } } public ObjectEditPage editObjects(String space, String page) { gotoPage(space, page, "edit", "editor=object"); return new ObjectEditPage(); } public ClassEditPage editClass(String space, String page) { gotoPage(space, page, "edit", "editor=class"); return new ClassEditPage(); } public String getVersion() throws Exception { Xwiki xwiki = rest().getResource("", null); return xwiki.getVersion(); } public String getMavenVersion() throws Exception { String version = getVersion(); int index = version.indexOf('-'); if (index > 0) { version = version.substring(0, index) + "-SNAPSHOT"; } return version; } public void attachFile(String space, String page, String name, File file, boolean failIfExists) throws Exception { InputStream is = new FileInputStream(file); try { attachFile(space, page, name, is, failIfExists); } finally { is.close(); } } /** * @since 5.1M2 */ public void attachFile(String space, String page, String name, InputStream is, boolean failIfExists, UsernamePasswordCredentials credentials) throws Exception { attachFile(Collections.singletonList(space), page, name, is, failIfExists, credentials); } /** * @since 7.2M2 */ public void attachFile(List<String> spaces, String page, String name, InputStream is, boolean failIfExists, UsernamePasswordCredentials credentials) throws Exception { UsernamePasswordCredentials currentCredentials = getDefaultCredentials(); try { if (credentials != null) { setDefaultCredentials(credentials); } attachFile(spaces, page, name, is, failIfExists); } finally { setDefaultCredentials(currentCredentials); } } public void attachFile(String space, String page, String name, InputStream is, boolean failIfExists) throws Exception { attachFile(Collections.singletonList(space), page, name, is, failIfExists); } /** * @since 7.2M2 */ public void attachFile(List<String> spaces, String page, String name, InputStream is, boolean failIfExists) throws Exception { AttachmentReference reference = new AttachmentReference(name, new DocumentReference(getCurrentWiki(), spaces, page)); attachFile(reference, is, failIfExists); } /** * @since 7.3M1 */ public void attachFile(EntityReference pageReference, String name, InputStream is, boolean failIfExists) throws Exception { EntityReference reference = new EntityReference(name, EntityType.ATTACHMENT, pageReference); attachFile(reference, is, failIfExists); } /** * @since 7.3M1 */ public void attachFile(EntityReference reference, Object is, boolean failIfExists) throws Exception { rest().attachFile(reference, is, failIfExists); } // FIXME: improve that with a REST API to directly import a XAR public void importXar(File file) throws Exception { // attach file attachFile("XWiki", "Import", file.getName(), file, false); // import file executeGet( getBaseBinURL() + "import/XWiki/Import?historyStrategy=add&importAsBackup=true&ajax&action=import&name=" + escapeURL(file.getName()), Status.OK.getStatusCode()); } /** * Delete the latest version from the history of a page, using the {@code /deleteversions/} action. * * @param space the space name of the page * @param page the name of the page * @since 7.0M2 */ public void deleteLatestVersion(String space, String page) { deleteVersion(space, page, "latest"); } /** * Delete a specific version from the history of a page, using the {@code /deleteversions/} action. * * @param space the space name of the page * @param page the name of the page * @param version the version to delete * @since 7.0M2 */ public void deleteVersion(String space, String page, String version) { deleteVersions(space, page, version, version); } /** * Delete an interval of versions from the history of a page, using the {@code /deleteversions/} action. * * @param space the space name of the page * @param page the name of the page * @param v1 the starting version to delete * @param v2 the ending version to delete * @since 7.0M2 */ public void deleteVersions(String space, String page, String v1, String v2) { gotoPage(space, page, "deleteversions", "rev1", v1, "rev2", v2, "confirm", "1"); } /** * Roll back a page to the previous version, using the {@code /rollback/} action. * * @param space the space name of the page * @param page the name of the page * @since 7.0M2 */ public void rollbackToPreviousVersion(String space, String page) { rollBackTo(space, page, "previous"); } /** * Roll back a page to the specified version, using the {@code /rollback/} action. * * @param space the space name of the page * @param page the name of the page * @param version the version to rollback to * @since 7.0M2 */ public void rollBackTo(String space, String page, String version) { gotoPage(space, page, "rollback", "rev", version, "confirm", "1"); } /** * Set the hierarchy mode used in the wiki * * @param mode the mode to use ("reference" or "parentchild") * @since 7.2M2 */ public void setHierarchyMode(String mode) { setPropertyInXWikiPreferences("core.hierarchyMode", "String", mode); } /** * Add and set a property into XWiki.XWikiPreferences. Create XWiki.XWikiPreferences if it does not exist. * * @param propertyName name of the property to set * @param propertyType the type of the property to add * @param value value to set to the property * @since 7.2M2 */ public void setPropertyInXWikiPreferences(String propertyName, String propertyType, Object value) { addClassProperty("XWiki", "XWikiPreferences", propertyName, propertyType); gotoPage("XWiki", "XWikiPreferences", "edit", "editor", "object"); ObjectEditPage objectEditPage = new ObjectEditPage(); if (objectEditPage.hasObject("XWiki.XWikiPreferences")) { updateObject("XWiki", "XWikiPreferences", "XWiki.XWikiPreferences", 0, propertyName, value); } else { addObject("XWiki", "XWikiPreferences", "XWiki.XWikiPreferences", propertyName, value); } } /** * @since 7.3M1 */ public static void assertStatuses(int actualCode, int... expectedCodes) { if (!ArrayUtils.contains(expectedCodes, actualCode)) { Assert.fail( "Unexpected code [" + actualCode + "], was expecting one of [" + Arrays.toString(expectedCodes) + "]"); } } /** * @since 7.3M1 */ public static <M extends HttpMethod> M assertStatusCodes(M method, boolean release, int... expectedCodes) { if (expectedCodes.length > 0) { assertStatuses(method.getStatusCode(), expectedCodes); } if (release) { method.releaseConnection(); } return method; } // HTTP /** * Encodes a given string so that it may be used as a URL component. Compatible with javascript decodeURIComponent, * though more strict than encodeURIComponent: all characters except [a-zA-Z0-9], '.', '-', '*', '_' are encoded. * Uses the same algorithm than the one used to generate URLs as otherwise tests won't find the proper matches... * See XWikiServletURLFactory#encodeWithinPath() and #encodeWithinQuery(). * * @param url the url to encode */ public String escapeURL(String url) { String encodedURL; try { encodedURL = URLEncoder.encode(url, "UTF-8"); } catch (Exception e) { // Should not happen (UTF-8 is always available) throw new RuntimeException("Missing charset [UTF-8]", e); } // The previous call will convert " " into "+" (and "+" into "%2B") so we need to convert "+" into "%20" // It's ok since %20 is allowed in both the URL path and the query string (and anchor). encodedURL = encodedURL.replaceAll("\\+", "%20"); return encodedURL; } /** * Usage example: * * <pre> * {@code * By.xpath("//a[. = " + escapeXPath(value) + "]") * } * </pre> * * @param value the value to escape * @return the escaped value */ public String escapeXPath(String value) { return "concat('" + value.replace("'", "', \"'\", '") + "', '')"; } public InputStream getInputStream(String path, Map<String, ?> queryParams) throws Exception { return getInputStream(getBaseURL(), path, queryParams); } public String getString(String path, Map<String, ?> queryParams) throws Exception { try (InputStream inputStream = getInputStream(getBaseURL(), path, queryParams)) { return IOUtils.toString(inputStream); } } public InputStream getInputStream(String prefix, String path, Map<String, ?> queryParams, Object... elements) throws Exception { String cleanPrefix = prefix.endsWith("/") ? prefix.substring(0, prefix.length() - 1) : prefix; if (path.startsWith(cleanPrefix)) { cleanPrefix = ""; } UriBuilder builder = UriBuilder.fromUri(cleanPrefix).path(path.startsWith("/") ? path.substring(1) : path); if (queryParams != null) { for (Map.Entry<String, ?> entry : queryParams.entrySet()) { if (entry.getValue() instanceof Object[]) { builder.queryParam(entry.getKey(), (Object[]) entry.getValue()); } else { builder.queryParam(entry.getKey(), entry.getValue()); } } } String url = builder.build(elements).toString(); return executeGet(url, Status.OK.getStatusCode()).getResponseBodyAsStream(); } protected GetMethod executeGet(String uri) throws Exception { GetMethod getMethod = new GetMethod(uri); this.httpClient.executeMethod(getMethod); return getMethod; } protected GetMethod executeGet(String uri, int... expectedCodes) throws Exception { return executeGet(uri, false, expectedCodes); } /** * @since 7.3M1 */ protected GetMethod executeGet(String uri, boolean release, int... expectedCodes) throws Exception { return assertStatusCodes(executeGet(uri), release, expectedCodes); } /** * @since 7.3M1 */ protected PostMethod executePost(String uri, InputStream content, String mediaType) throws Exception { PostMethod postMethod = new PostMethod(uri); RequestEntity entity = new InputStreamRequestEntity(content, mediaType); postMethod.setRequestEntity(entity); this.httpClient.executeMethod(postMethod); return postMethod; } protected PostMethod executePost(String uri, InputStream content, String mediaType, int... expectedCodes) throws Exception { return executePost(uri, content, mediaType, true, expectedCodes); } /** * @since 7.3M1 */ protected PostMethod executePost(String uri, InputStream content, String mediaType, boolean release, int... expectedCodes) throws Exception { return assertStatusCodes(executePost(uri, content, mediaType), false, expectedCodes); } /** * @since 7.3M1 */ protected DeleteMethod executeDelete(String uri) throws Exception { DeleteMethod postMethod = new DeleteMethod(uri); this.httpClient.executeMethod(postMethod); return postMethod; } /** * @since 7.3M1 */ protected void executeDelete(String uri, int... expectedCodes) throws Exception { assertStatusCodes(executeDelete(uri), true, expectedCodes); } /** * @since 7.3M1 */ protected PutMethod executePut(String uri, InputStream content, String mediaType) throws Exception { PutMethod putMethod = new PutMethod(uri); RequestEntity entity = new InputStreamRequestEntity(content, mediaType); putMethod.setRequestEntity(entity); this.httpClient.executeMethod(putMethod); return putMethod; } protected void executePut(String uri, InputStream content, String mediaType, int... expectedCodes) throws Exception { executePut(uri, content, mediaType, true, expectedCodes); } protected PutMethod executePut(String uri, InputStream content, String mediaType, boolean release, int... expectedCodes) throws Exception { return assertStatusCodes(executePut(uri, content, mediaType), release, expectedCodes); } // REST public RestTestUtils rest() { return this.rest; } /** * @since 7.3M1 */ // TODO: Refactor TestUtils to move RestTestUtils tools to xwiki-platform-test-integration public static class RestTestUtils { public static final Boolean ELEMENTS_ENCODED = new Boolean(true); public static final Map<EntityType, ResourceAPI> RESOURCES_MAP = new IdentityHashMap<>(); public static class ResourceAPI { public Class<?> api; public Class<?> localeAPI; public ResourceAPI(Class<?> api, Class<?> localeAPI) { this.api = api; this.localeAPI = localeAPI; } } /** * Used to match number part of the object reference name. */ private static final Pattern OBJECT_NAME_PATTERN = Pattern.compile("(\\\\*)\\[(\\d*)\\]$"); static { try { // Initialize REST related tools JAXBContext jaxbContext = JAXBContext .newInstance("org.xwiki.rest.model.jaxb:org.xwiki.extension.repository.xwiki.model.jaxb"); marshaller = jaxbContext.createMarshaller(); unmarshaller = jaxbContext.createUnmarshaller(); } catch (JAXBException e) { throw new RuntimeException(e); } RESOURCES_MAP.put(EntityType.DOCUMENT, new ResourceAPI(PageResource.class, PageTranslationResource.class)); RESOURCES_MAP.put(EntityType.OBJECT, new ResourceAPI(ObjectResource.class, null)); RESOURCES_MAP.put(EntityType.OBJECT_PROPERTY, new ResourceAPI(ObjectPropertyResource.class, null)); RESOURCES_MAP.put(EntityType.CLASS_PROPERTY, new ResourceAPI(ClassPropertyResource.class, null)); } /** * @since 7.3M1 */ public static org.xwiki.rest.model.jaxb.Object object(String className) { org.xwiki.rest.model.jaxb.Object obj = new org.xwiki.rest.model.jaxb.Object(); obj.setClassName(className); return obj; } /** * @since 7.3M1 */ public static String toPropertyString(Object value) { String stringValue; if (value instanceof Iterable) { StringBuilder builder = new StringBuilder(); for (Object item : (Iterable) value) { if (builder.length() > 0) { builder.append('|'); } builder.append(item); } stringValue = builder.toString(); } else if (value != null) { stringValue = value.toString(); } else { stringValue = null; } return stringValue; } /** * @since 7.3M1 */ public static Property property(String name, Object value) { Property property = new Property(); property.setName(name); property.setValue(toPropertyString(value)); return property; } private TestUtils testUtils; public RestTestUtils(TestUtils testUtils) { this.testUtils = testUtils; } public String getBaseURL() { return this.testUtils.getBaseURL() + "rest"; } private String toSpaceElement(Iterable<?> spaces) { StringBuilder builder = new StringBuilder(); for (Object space : spaces) { if (builder.length() > 0) { builder.append("/spaces/"); } if (space instanceof EntityReference) { builder.append(((EntityReference) space).getName()); } else { builder.append(space.toString()); } } return builder.toString(); } private String toSpaceElement(String spaceReference) { return toSpaceElement( relativeReferenceResolver.resolve(spaceReference, EntityType.SPACE).getReversedReferenceChain()); } protected Object[] toElements(Page page) { List<Object> elements = new ArrayList<>(); // Add wiki if (page.getWiki() != null) { elements.add(page.getWiki()); } else { elements.add(this.testUtils.getCurrentWiki()); } // Add spaces elements.add(toSpaceElement(page.getSpace())); // Add name elements.add(page.getName()); return elements.toArray(); } public Object[] toElements(org.xwiki.rest.model.jaxb.Object obj, boolean onlyDocument) { List<Object> elements = new ArrayList<>(); // Add wiki if (obj.getWiki() != null) { elements.add(obj.getWiki()); } else { elements.add(this.testUtils.getCurrentWiki()); } // Add spaces elements.add(toSpaceElement(obj.getSpace())); // Add name elements.add(obj.getPageName()); if (!onlyDocument) { // Add class elements.add(obj.getClassName()); // Add number elements.add(obj.getNumber()); } return elements.toArray(); } public Object[] toElements(EntityReference reference) { List<EntityReference> references = reference.getReversedReferenceChain(); List<Object> elements = new ArrayList<>(references.size() + 2); // Indicate that elements are already encoded elements.add(ELEMENTS_ENCODED); // Add current wiki if the reference does not contains any if (reference.extractReference(EntityType.WIKI) == null) { elements.add(this.testUtils.escapeURL(this.testUtils.getCurrentWiki())); } // Add reference for (EntityReference ref : references) { if (ref.getType() == EntityType.SPACE) { // The URI builder does not support multiple elements like space reference so we hack it by doing // the opposite of what is done when reading the URL (generate a value looking like // "space1/spaces/space2") Object value = elements.get(elements.size() - 1); StringBuilder builder; if (value instanceof StringBuilder) { builder = (StringBuilder) value; builder.append("/spaces/"); } else { builder = new StringBuilder(); elements.add(builder); } builder.append(this.testUtils.escapeURL(ref.getName())); } else if (ref.getType() == EntityType.OBJECT) { // The REST API is no in sync with the ObjectReference structure: // was is a unique name in ObjectReference is two separated class name and index in REST API String classReferenceStr; String objectNumberStr; Matcher matcher = OBJECT_NAME_PATTERN.matcher(ref.getName()); if (matcher.find()) { if (matcher.group(1).length() % 2 == 0) { classReferenceStr = ref.getName().substring(0, matcher.end(1)); objectNumberStr = matcher.group(2); } else { classReferenceStr = ref.getName(); objectNumberStr = null; } } else { classReferenceStr = ref.getName(); objectNumberStr = null; } elements.add(classReferenceStr); elements.add(objectNumberStr); } else { elements.add(this.testUtils.escapeURL(ref.getName())); } } // Add locale Locale locale = getLocale(reference); if (locale != null) { elements.add(locale); } return elements.toArray(); } /** * Add or update. */ public void save(Page page, int... expectedCodes) throws Exception { save(page, true, expectedCodes); } public EntityEnclosingMethod save(Page page, boolean release, int... expectedCodes) throws Exception { if (expectedCodes.length == 0) { // Allow create or modify by default expectedCodes = STATUS_CREATED_ACCEPTED; } return TestUtils.assertStatusCodes(executePut(PageResource.class, page, toElements(page)), release, expectedCodes); } /** * @since 7.3M1 */ public Page page(EntityReference reference) { Page page = new Page(); // Add current wiki if the reference does not contains any EntityReference wikiReference = reference.extractReference(EntityType.WIKI); if (wikiReference == null) { page.setWiki(this.testUtils.getCurrentWiki()); } else { page.setWiki(wikiReference.getName()); } // Add spaces EntityReference spaceReference = reference.extractReference(EntityType.SPACE).removeParent(wikiReference); page.setSpace(referenceSerializer.serialize(spaceReference)); // Add page EntityReference documentReference = reference.extractReference(EntityType.DOCUMENT); page.setName(documentReference.getName()); return page; } /** * @since 7.3M1 */ public void savePage(EntityReference reference) throws Exception { savePage(reference, null, null, null, null); } /** * @since 7.3M1 */ public void savePage(EntityReference reference, String content, String title) throws Exception { savePage(reference, content, null, title, null); } /** * @since 7.3M1 */ public void savePage(EntityReference reference, String content, String syntaxId, String title, String parentFullPageName) throws Exception { Page page = page(reference); if (content != null) { page.setContent(content); } if (title != null) { page.setTitle(title); } if (syntaxId != null) { page.setSyntax(syntaxId); } if (parentFullPageName != null) { page.setParent(parentFullPageName); } save(page, true); } /** * Add a new object. */ public void add(org.xwiki.rest.model.jaxb.Object obj) throws Exception { add(obj, true); } /** * Add a new object. */ public EntityEnclosingMethod add(org.xwiki.rest.model.jaxb.Object obj, boolean release) throws Exception { return TestUtils.assertStatusCodes(executePost(ObjectsResource.class, obj, toElements(obj, true)), release, STATUS_CREATED); } /** * Fail if the object does not exist. */ public void update(org.xwiki.rest.model.jaxb.Object obj) throws Exception { update(obj, true); } /** * Fail if the object does not exist. */ public EntityEnclosingMethod update(org.xwiki.rest.model.jaxb.Object obj, boolean release) throws Exception { return TestUtils.assertStatusCodes(executePut(ObjectResource.class, obj, toElements(obj, false)), release, STATUS_CREATED_ACCEPTED); } public void delete(EntityReference reference) throws Exception { Class<?> resource = getResourceAPI(reference); TestUtils.assertStatusCodes(executeDelete(resource, toElements(reference)), true, STATUS_NO_CONTENT_NOT_FOUND); } // TODO: make EntityReference#getParameter() public private Locale getLocale(EntityReference reference) { if (reference instanceof DocumentReference) { return ((DocumentReference) reference).getLocale(); } else if (reference instanceof LocalDocumentReference) { return ((LocalDocumentReference) reference).getLocale(); } return null; } public void deletePage(String space, String page) throws Exception { delete(new LocalDocumentReference(space, page)); } /** * @since 9.0RC1 */ public void deletePage(String space, String page, Locale locale) throws Exception { delete(new LocalDocumentReference(space, page, locale)); } /** * @since 8.0M1 */ public void attachFile(EntityReference reference, Object is, boolean failIfExists) throws Exception { // make sure the page exist if (!exists(reference.getParent())) { savePage(reference.getParent()); } if (failIfExists) { assertStatusCodes(executePut(AttachmentResource.class, is, toElements(reference)), true, STATUS_CREATED); } else { assertStatusCodes(executePut(AttachmentResource.class, is, toElements(reference)), true, STATUS_CREATED_ACCEPTED); } } public boolean exists(EntityReference reference) throws Exception { GetMethod getMethod = executeGet(reference); getMethod.releaseConnection(); return getMethod.getStatusCode() == Status.OK.getStatusCode(); } /** * Return object model of the passed reference. Fail if none could be found. * * @since 7.3 */ public <T> T get(EntityReference reference) throws Exception { return get(reference, true); } /** * Return object model of the passed reference or null if none could be found. * * @since 8.0M1 */ public <T> T get(EntityReference reference, boolean failIfNotFound) throws Exception { Class<?> resource = getResourceAPI(reference); return get(resource, reference, failIfNotFound); } /** * @since 9.0RC1 */ public Class<?> getResourceAPI(EntityReference reference) throws Exception { ResourceAPI resource = RESOURCES_MAP.get(reference.getType()); if (resource == null) { throw new Exception("Unsuported type [" + reference.getType() + "]"); } return getLocale(reference) != null ? resource.localeAPI : resource.api; } /** * Return object model of the passed reference with the passed resource URI. Fail if none could be found. * * @since 8.0M1 */ public <T> T get(Object resourceURI, EntityReference reference) throws Exception { return get(resourceURI, reference, true); } /** * Return object model of the passed reference with the passed resource URI or null if none could be found. * * @since 8.0M1 */ public <T> T get(Object resourceURI, EntityReference reference, boolean failIfNotFound) throws Exception { GetMethod getMethod = assertStatusCodes(executeGet(resourceURI, reference), false, failIfNotFound ? STATUS_OK : STATUS_OK_NOT_FOUND); if (getMethod.getStatusCode() == Status.NOT_FOUND.getStatusCode()) { return null; } if (reference.getType() == EntityType.ATTACHMENT) { return (T) getMethod.getResponseBodyAsStream(); } else { try { try (InputStream stream = getMethod.getResponseBodyAsStream()) { return toResource(stream); } } finally { getMethod.releaseConnection(); } } } public InputStream getInputStream(String resourceUri, Map<String, ?> queryParams, Object... elements) throws Exception { return this.testUtils.getInputStream(getBaseURL(), resourceUri, queryParams, elements); } public InputStream postRESTInputStream(Object resourceUri, Object restObject, Object... elements) throws Exception { return postInputStream(resourceUri, restObject, Collections.<String, Object[]>emptyMap(), elements); } public InputStream postInputStream(Object resourceUri, Object restObject, Map<String, Object[]> queryParams, Object... elements) throws Exception { return executePost(resourceUri, restObject, queryParams, elements).getResponseBodyAsStream(); } public <T> T toResource(InputStream is) throws JAXBException { return (T) unmarshaller.unmarshal(is); } protected InputStream toResourceInputStream(Object restObject) throws JAXBException { InputStream resourceStream; if (restObject instanceof InputStream) { resourceStream = (InputStream) restObject; } else if (restObject instanceof byte[]) { resourceStream = new ByteArrayInputStream((byte[]) restObject); } else { ByteArrayOutputStream stream = new ByteArrayOutputStream(); marshaller.marshal(restObject, stream); resourceStream = new ByteArrayInputStream(stream.toByteArray()); } return resourceStream; } /** * @since 7.3 */ public GetMethod executeGet(EntityReference reference) throws Exception { Class<?> resource = getResourceAPI(reference); return executeGet(resource, reference); } /** * @since 8.0M1 */ public GetMethod executeGet(Object resourceURI, EntityReference reference) throws Exception { return executeGet(resourceURI, toElements(reference)); } public GetMethod executeGet(Object resourceUri, Object... elements) throws Exception { return executeGet(resourceUri, Collections.<String, Object[]>emptyMap(), elements); } public GetMethod executeGet(Object resourceUri, Map<String, Object[]> queryParams, Object... elements) throws Exception { // Build URI String uri = createUri(resourceUri, queryParams, elements).toString(); return this.testUtils.executeGet(uri); } public PostMethod executePost(Object resourceUri, Object restObject, Object... elements) throws Exception { return executePost(resourceUri, restObject, Collections.<String, Object[]>emptyMap(), elements); } public PostMethod executePost(Object resourceUri, Object restObject, Map<String, Object[]> queryParams, Object... elements) throws Exception { // Build URI String uri = createUri(resourceUri, queryParams, elements).toString(); try (InputStream resourceStream = toResourceInputStream(restObject)) { return this.testUtils.executePost(uri, resourceStream, MediaType.APPLICATION_XML); } } public PutMethod executePut(Object resourceUri, Object restObject, Object... elements) throws Exception { return executePut(resourceUri, restObject, Collections.<String, Object[]>emptyMap(), elements); } public PutMethod executePut(Object resourceUri, Object restObject, Map<String, Object[]> queryParams, Object... elements) throws Exception { // Build URI String uri = createUri(resourceUri, queryParams, elements).toString(); try (InputStream resourceStream = toResourceInputStream(restObject)) { return this.testUtils.executePut(uri, resourceStream, MediaType.APPLICATION_XML); } } public DeleteMethod executeDelete(Object resourceUri, Object... elements) throws Exception { return executeDelete(resourceUri, Collections.<String, Object[]>emptyMap(), elements); } public DeleteMethod executeDelete(Object resourceUri, Map<String, Object[]> queryParams, Object... elements) throws Exception { // Build URI String uri = createUri(resourceUri, queryParams, elements).toString(); return this.testUtils.executeDelete(uri); } public URI createUri(Object resourceUri, Map<String, Object[]> queryParams, Object... elements) { if (resourceUri instanceof URI) { return (URI) resourceUri; } // Create URI builder UriBuilder builder = getUriBuilder(resourceUri, queryParams); // Build URI URI uri; if (elements.length > 0 && elements[0] == ELEMENTS_ENCODED) { uri = builder.buildFromEncoded(Arrays.copyOfRange(elements, 1, elements.length)); } else { uri = builder.build(elements); } return uri; } public UriBuilder getUriBuilder(Object resourceUri, Map<String, Object[]> queryParams) { // Create URI builder UriBuilder builder; if (resourceUri instanceof Class) { builder = getUriBuilder((Class) resourceUri); } else { String stringResourceUri = (String) resourceUri; builder = UriBuilder.fromUri(getBaseURL().substring(0, getBaseURL().length() - 1)) .path(!stringResourceUri.isEmpty() && stringResourceUri.charAt(0) == '/' ? stringResourceUri.substring(1) : stringResourceUri); } // Add query parameters if (queryParams != null) { for (Map.Entry<String, Object[]> entry : queryParams.entrySet()) { builder.queryParam(entry.getKey(), entry.getValue()); } } return builder; } protected UriBuilder getUriBuilder(Class<?> resource) { return UriBuilder.fromUri(getBaseURL()).path(resource); } public byte[] getBuffer(String resourceUri, Map<String, Object[]> queryParams, Object... elements) throws Exception { InputStream is = getInputStream(resourceUri, queryParams, elements); byte[] buffer; try { buffer = IOUtils.toByteArray(is); } finally { is.close(); } return buffer; } public <T> T getResource(String resourceUri, Map<String, Object[]> queryParams, Object... elements) throws Exception { T resource; try (InputStream is = getInputStream(resourceUri, queryParams, elements)) { resource = (T) unmarshaller.unmarshal(is); } return resource; } } }