001/* 002 * Licensed to DuraSpace under one or more contributor license agreements. 003 * See the NOTICE file distributed with this work for additional information 004 * regarding copyright ownership. 005 * 006 * DuraSpace licenses this file to you under the Apache License, 007 * Version 2.0 (the "License"); you may not use this file except in 008 * compliance with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.fcrepo.integration; 019 020import static com.gargoylesoftware.htmlunit.BrowserVersion.FIREFOX; 021import static com.google.common.collect.Lists.transform; 022import static java.util.UUID.randomUUID; 023import static javax.ws.rs.core.HttpHeaders.ACCEPT; 024import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; 025import static javax.ws.rs.core.Response.Status.NOT_FOUND; 026import static org.fcrepo.kernel.api.FedoraTypes.FCR_METADATA; 027import static org.fcrepo.kernel.api.RdfLexicon.REPOSITORY_NAMESPACE; 028import static org.junit.Assert.assertEquals; 029import static org.junit.Assert.assertNotEquals; 030import static org.junit.Assert.assertNotNull; 031import static org.junit.Assert.assertTrue; 032import static org.junit.Assert.fail; 033 034import java.io.IOException; 035import java.nio.charset.StandardCharsets; 036import java.util.List; 037import java.util.concurrent.TimeUnit; 038 039import org.apache.http.HttpResponse; 040import org.apache.http.auth.AuthScope; 041import org.apache.http.client.CredentialsProvider; 042import org.apache.http.client.methods.HttpPatch; 043import org.apache.http.entity.StringEntity; 044import org.junit.After; 045import org.junit.Before; 046import org.junit.Test; 047 048import com.gargoylesoftware.htmlunit.DefaultCredentialsProvider; 049import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; 050import com.gargoylesoftware.htmlunit.IncorrectnessListener; 051import com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController; 052import com.gargoylesoftware.htmlunit.Page; 053import com.gargoylesoftware.htmlunit.SilentCssErrorHandler; 054import com.gargoylesoftware.htmlunit.WebClient; 055import com.gargoylesoftware.htmlunit.html.DomAttr; 056import com.gargoylesoftware.htmlunit.html.DomElement; 057import com.gargoylesoftware.htmlunit.html.DomText; 058import com.gargoylesoftware.htmlunit.html.HtmlButton; 059import com.gargoylesoftware.htmlunit.html.HtmlElement; 060import com.gargoylesoftware.htmlunit.html.HtmlFileInput; 061import com.gargoylesoftware.htmlunit.html.HtmlForm; 062import com.gargoylesoftware.htmlunit.html.HtmlInput; 063import com.gargoylesoftware.htmlunit.html.HtmlPage; 064import com.gargoylesoftware.htmlunit.html.HtmlSelect; 065import com.gargoylesoftware.htmlunit.html.HtmlTextArea; 066 067/** 068 * <p>FedoraHtmlResponsesIT class.</p> 069 * 070 * @author cbeer 071 */ 072public class FedoraHtmlResponsesIT extends AbstractResourceIT { 073 074 private WebClient webClient; 075 private WebClient javascriptlessWebClient; 076 077 @Before 078 public void setUp() { 079 webClient = getDefaultWebClient(); 080 081 javascriptlessWebClient = getDefaultWebClient(); 082 javascriptlessWebClient.getOptions().setJavaScriptEnabled(false); 083 } 084 085 @After 086 public void cleanUp() { 087 webClient.close(); 088 javascriptlessWebClient.close(); 089 } 090 091 @Test 092 public void testDescribeHtml() throws IOException { 093 final HtmlPage page = webClient.getPage(serverAddress); 094 ((HtmlElement)page.getFirstByXPath("//h4[text()='Properties']")).click(); 095 096 checkForHeaderBranding(page); 097 final String namespaceLabel = page 098 .getFirstByXPath("//span[@title='" + REPOSITORY_NAMESPACE + "']/text()") 099 .toString(); 100 101 assertEquals("Expected to find namespace URIs displayed as their prefixes", "fedora:", 102 namespaceLabel); 103 } 104 105 @Test 106 public void testCreateNewNodeWithProvidedId() throws IOException { 107 createAndVerifyObjectWithIdFromRootPage(newPid()); 108 } 109 110 private String newPid() { 111 return randomUUID().toString(); 112 } 113 114 private HtmlPage createAndVerifyObjectWithIdFromRootPage(final String pid) throws IOException { 115 return createAndVerifyObjectWithIdFromRootPage(pid, "basic container"); 116 } 117 118 private HtmlPage createAndVerifyObjectWithIdFromRootPage(final String pid, final String containerType) 119 throws IOException { 120 final HtmlPage page = webClient.getPage(serverAddress); 121 final HtmlForm form = (HtmlForm)page.getElementById("action_create"); 122 final HtmlSelect type = (HtmlSelect)page.getElementById("new_mixin"); 123 type.getOptionByValue(containerType).setSelected(true); 124 125 final HtmlInput new_id = (HtmlInput)page.getElementById("new_id"); 126 new_id.setValueAttribute(pid); 127 final HtmlButton button = form.getFirstByXPath("button"); 128 button.click(); 129 130 131 try { 132 final HtmlPage page1 = webClient.getPage(serverAddress + pid); 133 assertEquals("Page had wrong title!", serverAddress + pid, page1.getTitleText()); 134 return page1; 135 } catch (final FailingHttpStatusCodeException e) { 136 fail("Did not successfully retrieve created page! Got HTTP code: " + e.getStatusCode()); 137 return null; 138 } 139 } 140 141 @Test 142 public void testCreateNewNodeWithGeneratedId() throws IOException { 143 144 final HtmlPage page = webClient.getPage(serverAddress); 145 final HtmlForm form = (HtmlForm)page.getElementById("action_create"); 146 final HtmlSelect type = (HtmlSelect)page.getElementById("new_mixin"); 147 type.getOptionByValue("basic container").setSelected(true); 148 final HtmlButton button = form.getFirstByXPath("button"); 149 button.click(); 150 151 final HtmlPage page1 = javascriptlessWebClient.getPage(serverAddress); 152 assertTrue("Didn't see new information in page!", !page1.asText().equals(page.asText())); 153 } 154 155 @Test 156 public void testCreateNewBasicContainer() throws IOException { 157 final HtmlPage newPage = createAndVerifyObjectWithIdFromRootPage(newPid(), "basic container"); 158 assertTrue("Set container type to ldp:BasicContainer", newPage.asText().contains( 159 "http://www.w3.org/ns/ldp#BasicContainer")); 160 } 161 162 @Test 163 public void testCreateNewDirectContainer() throws IOException { 164 final HtmlPage newPage = createAndVerifyObjectWithIdFromRootPage(newPid(), "direct container"); 165 assertTrue("Set container type to ldp:DirectContainer", newPage.asText().contains( 166 "http://www.w3.org/ns/ldp#DirectContainer")); 167 } 168 169 @Test 170 public void testCreateNewIndirectContainer() throws IOException { 171 final HtmlPage newPage = createAndVerifyObjectWithIdFromRootPage(newPid(), "indirect container"); 172 assertTrue("Set container type to ldp:IndirectContainer", newPage.asText().contains( 173 "http://www.w3.org/ns/ldp#IndirectContainer")); 174 } 175 176 @Test 177 public void testCreateNewDatastream() throws Exception { 178 179 // can't do this with javascript, because HTMLUnit doesn't speak the HTML5 file api 180 final HtmlPage page = javascriptlessWebClient.getPage(serverAddress); 181 182 final HtmlSelect type = (HtmlSelect)page.getElementById("new_mixin"); 183 type.getOptionByValue("binary").setSelected(true); 184 185 final HtmlFileInput fileInput = (HtmlFileInput)page.getElementById("binary_payload"); 186 fileInput.setData("abcdef".getBytes()); 187 fileInput.setContentType("application/pdf"); 188 189 final HtmlButton button = (HtmlButton)page.getElementById("btn_action_create"); 190 button.click(); 191 192 // Without Javascript you end up at a blank page with just the newly generated URI as text. 193 final Page resultPage = javascriptlessWebClient.getCurrentWindow().getEnclosedPage(); 194 final String newUri = resultPage.getWebResponse().getContentAsString(); 195 196 final Page page1 = javascriptlessWebClient.getPage(newUri + "/" + FCR_METADATA); 197 assertTrue(page1.isHtmlPage()); 198 assertEquals(newUri, ((HtmlPage)page1).getTitleText()); 199 } 200 201 @Test 202 public void testCreateNewDatastreamWithNoFileAttached() throws IOException { 203 204 final String pid = newPid(); 205 206 // can't do this with javascript, because HTMLUnit doesn't speak the HTML5 file api 207 final HtmlPage page = webClient.getPage(serverAddress); 208 final HtmlForm form = (HtmlForm)page.getElementById("action_create"); 209 210 final HtmlInput slug = form.getInputByName("slug"); 211 slug.setValueAttribute(pid); 212 213 final HtmlSelect type = (HtmlSelect)page.getElementById("new_mixin"); 214 type.getOptionByValue("binary").setSelected(true); 215 216 final HtmlButton button = form.getFirstByXPath("button"); 217 button.click(); 218 219 javascriptlessWebClient.getOptions().setThrowExceptionOnFailingStatusCode(false); 220 final int status = javascriptlessWebClient.getPage(serverAddress + pid).getWebResponse().getStatusCode(); 221 assertEquals(NOT_FOUND.getStatusCode(), status); 222 } 223 224 @Test 225 public void testCreateNewObjectAndDeleteIt() throws IOException { 226 final boolean throwExceptionOnFailingStatusCode = webClient.getOptions().isThrowExceptionOnFailingStatusCode(); 227 webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); 228 229 final String pid = createNewObject(); 230 final HtmlPage page = webClient.getPage(serverAddress + pid); 231 final HtmlForm action_delete = page.getFormByName("action_delete"); 232 action_delete.getButtonByName("delete-button").click(); 233 webClient.waitForBackgroundJavaScript(1000); 234 webClient.waitForBackgroundJavaScriptStartingBefore(10000); 235 236 final Page page2 = webClient.getPage(serverAddress + pid); 237 assertEquals("Didn't get a 410!", 410, page2.getWebResponse() 238 .getStatusCode()); 239 240 webClient.getOptions().setThrowExceptionOnFailingStatusCode(throwExceptionOnFailingStatusCode); 241 } 242 243 @Test 244 public void testVersionsListWorksWhenNoVersionsPresent() throws IOException { 245 final boolean throwExceptionOnFailingStatusCode = webClient.getOptions().isThrowExceptionOnFailingStatusCode(); 246 webClient.getOptions().setThrowExceptionOnFailingStatusCode(true); 247 248 final String pid = createNewObject(); 249 final HtmlPage page = webClient.getPage(serverAddress + pid); 250 final DomElement viewVersions = page.getElementById("view_versions"); 251 final Page versionsPage = viewVersions.click(); 252 assertEquals("Didn't get a 200!", 200, versionsPage.getWebResponse() 253 .getStatusCode()); 254 webClient.getOptions().setThrowExceptionOnFailingStatusCode(throwExceptionOnFailingStatusCode); 255 } 256 257 /** 258 * This test walks through the steps for creating an object, setting some 259 * metadata, creating a version, updating that metadata, viewing the 260 * version history to find that old version. 261 * 262 * @throws IOException exception thrown during this function 263 */ 264 @Test 265 public void testVersionCreationAndNavigation() throws Exception { 266 final String pid = newPid(); 267 createAndVerifyObjectWithIdFromRootPage(pid); 268 269 TimeUnit.SECONDS.sleep(1); 270 271 final String updateSparql = "PREFIX dc: <http://purl.org/dc/elements/1.1/>\n" + 272 "PREFIX fedora: <" + REPOSITORY_NAMESPACE + ">\n" + 273 "\n" + 274 "INSERT DATA { <> dc:title \"Object Title\". }"; 275 postSparqlUpdateUsingHttpClient(updateSparql, pid); 276 277 final HtmlPage objectPage = javascriptlessWebClient.getPage(serverAddress + pid); 278 assertEquals("Title should be set.", "Object Title", 279 objectPage.getFirstByXPath("//span[@property='http://purl.org/dc/elements/1.1/title']/text()") 280 .toString()); 281 282 TimeUnit.SECONDS.sleep(1); 283 final String updateSparql2 = "PREFIX dc: <http://purl.org/dc/elements/1.1/>\n" + 284 "\n" + 285 "DELETE { <> dc:title ?t }\n" + 286 "INSERT { <> dc:title \"Updated Title\" }" + 287 "WHERE { <> dc:title ?t }"; 288 postSparqlUpdateUsingHttpClient(updateSparql2, pid); 289 290 final HtmlPage versions = 291 javascriptlessWebClient.getPage(serverAddress + pid + "/fcr:versions"); 292 final List<DomAttr> versionLinks = 293 castList(versions.getByXPath("//a[@class='version_link']/@href")); 294 assertEquals("There should be three versions.", 3, versionLinks.size()); 295 296 // get the labels 297 // will look like "Version from 2013-00-0T00:00:00.000Z" 298 // and will sort chronologically based on a String comparison 299 final List<DomText> labels = 300 castList(versions 301 .getByXPath("//a[@class='version_link']/text()")); 302 final boolean chronological = labels.get(0).asText().compareTo(labels.get(1).toString()) < 0; 303 logger.debug("Versions {} in chronological order: {}, {}", 304 chronological ? "are" : "are not", labels.get(0).asText(), labels.get(1).asText()); 305 306 final HtmlPage firstRevision = 307 javascriptlessWebClient.getPage(versionLinks.get(chronological ? 1 : 2) 308 .getNodeValue()); 309 final List<DomText> v1Titles = 310 castList(firstRevision 311 .getByXPath("//span[@property='http://purl.org/dc/elements/1.1/title']/text()")); 312 final HtmlPage secondRevision = 313 javascriptlessWebClient.getPage(versionLinks.get(chronological ? 2 : 1) 314 .getNodeValue()); 315 final List<DomText> v2Titles = 316 castList(secondRevision 317 .getByXPath("//span[@property='http://purl.org/dc/elements/1.1/title']/text()")); 318 319 assertEquals("Version one should have one title.", 1, v1Titles.size()); 320 assertEquals("Version two should have one title.", 1, v2Titles.size()); 321 assertNotEquals("Each version should have a different title.", v1Titles.get(0), v2Titles.get(0)); 322 assertEquals("First version should be preserved.", "Object Title", v1Titles.get(0).getWholeText()); 323 assertEquals("Second version should be preserved.", "Updated Title", v2Titles.get(0).getWholeText()); 324 } 325 326 private static void postSparqlUpdateUsingHttpClient(final String sparql, final String pid) throws IOException { 327 final HttpPatch method = new HttpPatch(serverAddress + pid); 328 method.addHeader(CONTENT_TYPE, "application/sparql-update"); 329 final StringEntity entity = new StringEntity(sparql, StandardCharsets.UTF_8); 330 method.setEntity(entity); 331 final HttpResponse response = client.execute(method); 332 assertEquals("Expected successful response.", 204, 333 response.getStatusLine().getStatusCode()); 334 } 335 336 @Test 337 public void testCreateNewObjectAndSetProperties() throws Exception { 338 final String pid = createNewObject(); 339 340 final HtmlPage page = webClient.getPage(serverAddress + pid); 341 final HtmlForm form = (HtmlForm)page.getElementById("action_sparql_update"); 342 final HtmlTextArea sparql_update_query = (HtmlTextArea)page.getElementById("sparql_update_query"); 343 sparql_update_query.setText("INSERT { <> <info:some-predicate> 'asdf' } WHERE { }"); 344 345 final HtmlButton button = form.getFirstByXPath("button"); 346 button.click(); 347 348 final HtmlPage page1 = webClient.getPage(serverAddress + pid); 349 assertTrue(page1.getElementById("metadata").asText().contains("some-predicate")); 350 } 351 352 @Test 353 public void testSimpleSearch() throws Exception { 354 final HtmlPage page = webClient.getPage(serverAddress); 355 page.getAnchorByText("Search").click(); 356 357 final HtmlPage searchPage = (HtmlPage)webClient.getCurrentWindow().getEnclosedPage(); 358 359 final HtmlForm form = (HtmlForm)searchPage.getElementById("action_search"); 360 final HtmlInput q = (HtmlInput)searchPage.getElementById("search_value_1"); 361 q.setValueAttribute("info:fedora/*"); 362 final HtmlButton button = form.getFirstByXPath("button"); 363 button.click(); 364 } 365 366 367 private static void checkForHeaderBranding(final HtmlPage page) { 368 assertNotNull( 369 page.getFirstByXPath("//nav[@role='navigation']/div[@class='navbar-header']/a[@class='navbar-brand']")); 370 } 371 372 private String createNewObject() throws IOException { 373 374 final String pid = newPid(); 375 376 final HtmlPage page = webClient.getPage(serverAddress); 377 final HtmlForm form = page.getFormByName("action_create"); 378 379 final HtmlInput slug = form.getInputByName("slug"); 380 slug.setValueAttribute(pid); 381 382 final HtmlButton button = form.getFirstByXPath("button"); 383 button.click(); 384 385 webClient.waitForBackgroundJavaScript(1000); 386 webClient.waitForBackgroundJavaScriptStartingBefore(10000); 387 return pid; 388 } 389 390 private CredentialsProvider getFedoraAdminCredentials() { 391 final CredentialsProvider credentials = new DefaultCredentialsProvider(); 392 credentials.setCredentials(AuthScope.ANY, FEDORA_ADMIN_CREDENTIALS); 393 return credentials; 394 } 395 396 private WebClient getDefaultWebClient() { 397 398 final WebClient webClient = new WebClient(FIREFOX); 399 webClient.addRequestHeader(ACCEPT, "text/html"); 400 webClient.setCredentialsProvider(getFedoraAdminCredentials()); 401 webClient.waitForBackgroundJavaScript(1000); 402 webClient.waitForBackgroundJavaScriptStartingBefore(10000); 403 webClient.setAjaxController(new NicelyResynchronizingAjaxController()); 404 //Suppress warning from IncorrectnessListener 405 webClient.setIncorrectnessListener(new SuppressWarningIncorrectnessListener()); 406 //Suppress css warning with the silent error handler. 407 webClient.setCssErrorHandler(new SilentCssErrorHandler()); 408 return webClient; 409 410 } 411 412 @SuppressWarnings("unchecked") 413 private static <T> List<T> castList(final List<?> l) { 414 return transform(l, x -> (T) x); 415 } 416 417 private static class SuppressWarningIncorrectnessListener 418 implements IncorrectnessListener { 419 @Override 420 public void notify(final String arg0, final Object arg1) { 421 422 } 423 } 424 425}