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_60; 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.RdfLexicon.FEDORA_CONFIG_NAMESPACE; 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.ByteArrayInputStream; 035import java.io.IOException; 036import java.util.List; 037 038import com.gargoylesoftware.htmlunit.html.DomElement; 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.BasicHttpEntity; 044import org.junit.After; 045import org.junit.Before; 046import org.junit.Ignore; 047import org.junit.Test; 048 049import com.gargoylesoftware.htmlunit.DefaultCredentialsProvider; 050import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; 051import com.gargoylesoftware.htmlunit.IncorrectnessListener; 052import com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController; 053import com.gargoylesoftware.htmlunit.Page; 054import com.gargoylesoftware.htmlunit.SilentCssErrorHandler; 055import com.gargoylesoftware.htmlunit.WebClient; 056import com.gargoylesoftware.htmlunit.html.DomAttr; 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 @Ignore("The htmlunit web client can't handle the HTML5 file API") 178 public void testCreateNewDatastream() throws IOException { 179 180 final String pid = randomUUID().toString(); 181 182 // can't do this with javascript, because HTMLUnit doesn't speak the HTML5 file api 183 final HtmlPage page = webClient.getPage(serverAddress); 184 final HtmlForm form = (HtmlForm)page.getElementById("action_create"); 185 186 final HtmlInput slug = form.getInputByName("slug"); 187 slug.setValueAttribute(pid); 188 189 final HtmlSelect type = (HtmlSelect)page.getElementById("new_mixin"); 190 type.getOptionByValue("binary").setSelected(true); 191 192 final HtmlFileInput fileInput = (HtmlFileInput)page.getElementById("datastream_payload"); 193 fileInput.setData("abcdef".getBytes()); 194 fileInput.setContentType("application/pdf"); 195 196 final HtmlButton button = form.getFirstByXPath("button"); 197 button.click(); 198 199 final HtmlPage page1 = javascriptlessWebClient.getPage(serverAddress + pid); 200 assertEquals(serverAddress + pid, page1.getTitleText()); 201 } 202 203 @Test 204 public void testCreateNewDatastreamWithNoFileAttached() throws IOException { 205 206 final String pid = randomUUID().toString(); 207 208 // can't do this with javascript, because HTMLUnit doesn't speak the HTML5 file api 209 final HtmlPage page = webClient.getPage(serverAddress); 210 final HtmlForm form = (HtmlForm)page.getElementById("action_create"); 211 212 final HtmlInput slug = form.getInputByName("slug"); 213 slug.setValueAttribute(pid); 214 215 final HtmlSelect type = (HtmlSelect)page.getElementById("new_mixin"); 216 type.getOptionByValue("binary").setSelected(true); 217 218 final HtmlButton button = form.getFirstByXPath("button"); 219 button.click(); 220 221 javascriptlessWebClient.getOptions().setThrowExceptionOnFailingStatusCode(false); 222 final int status = javascriptlessWebClient.getPage(serverAddress + pid).getWebResponse().getStatusCode(); 223 assertEquals(NOT_FOUND.getStatusCode(), status); 224 } 225 226 @Test 227 public void testCreateNewObjectAndDeleteIt() throws IOException { 228 final boolean throwExceptionOnFailingStatusCode = webClient.getOptions().isThrowExceptionOnFailingStatusCode(); 229 webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); 230 231 final String pid = createNewObject(); 232 final HtmlPage page = webClient.getPage(serverAddress + pid); 233 final HtmlForm action_delete = page.getFormByName("action_delete"); 234 action_delete.getButtonByName("delete-button").click(); 235 webClient.waitForBackgroundJavaScript(1000); 236 webClient.waitForBackgroundJavaScriptStartingBefore(10000); 237 238 final Page page2 = webClient.getPage(serverAddress + pid); 239 assertEquals("Didn't get a 410!", 410, page2.getWebResponse() 240 .getStatusCode()); 241 242 webClient.getOptions().setThrowExceptionOnFailingStatusCode(throwExceptionOnFailingStatusCode); 243 } 244 245 @Test 246 public void testVersionsListWorksWhenNoVersionsPresent() throws IOException { 247 final boolean throwExceptionOnFailingStatusCode = webClient.getOptions().isThrowExceptionOnFailingStatusCode(); 248 webClient.getOptions().setThrowExceptionOnFailingStatusCode(true); 249 250 final String pid = createNewObject(); 251 final HtmlPage page = webClient.getPage(serverAddress + pid); 252 final DomElement viewVersions = page.getElementById("view_versions"); 253 final Page versionsPage = viewVersions.click(); 254 assertEquals("Didn't get a 200!", 200, versionsPage.getWebResponse() 255 .getStatusCode()); 256 webClient.getOptions().setThrowExceptionOnFailingStatusCode(throwExceptionOnFailingStatusCode); 257 } 258 259 /** 260 * This test walks through the steps for creating an object, setting some 261 * metadata, creating a version, updating that metadata, viewing the 262 * version history to find that old version. 263 * 264 * @throws IOException exception thrown during this function 265 */ 266 @Ignore 267 @Test 268 public void testVersionCreationAndNavigation() throws IOException { 269 final String pid = randomUUID().toString(); 270 createAndVerifyObjectWithIdFromRootPage(pid); 271 272 final String updateSparql = "PREFIX dc: <http://purl.org/dc/elements/1.1/>\n" + 273 "PREFIX fedora: <" + REPOSITORY_NAMESPACE + ">\n" + 274 "\n" + 275 "INSERT DATA { <> fedoraconfig:versioningPolicy \"auto-version\" ; dc:title \"Object Title\". }"; 276 postSparqlUpdateUsingHttpClient(updateSparql, pid); 277 278 final HtmlPage objectPage = javascriptlessWebClient.getPage(serverAddress + pid); 279 assertEquals("Auto versioning should be set.", "auto-version", 280 objectPage.getFirstByXPath( 281 "//span[@property='" + FEDORA_CONFIG_NAMESPACE + "versioningPolicy']/text()") 282 .toString()); 283 assertEquals("Title should be set.", "Object Title", 284 objectPage.getFirstByXPath("//span[@property='http://purl.org/dc/elements/1.1/title']/text()") 285 .toString()); 286 287 final String updateSparql2 = "PREFIX dc: <http://purl.org/dc/elements/1.1/>\n" + 288 "\n" + 289 "DELETE { <> dc:title ?t }\n" + 290 "INSERT { <> dc:title \"Updated Title\" }" + 291 "WHERE { <> dc:title ?t }"; 292 postSparqlUpdateUsingHttpClient(updateSparql2, pid); 293 294 final HtmlPage versions = 295 javascriptlessWebClient.getPage(serverAddress + pid + "/fcr:versions"); 296 final List<DomAttr> versionLinks = 297 castList(versions.getByXPath("//a[@class='version_link']/@href")); 298 assertEquals("There should be two revisions.", 2, versionLinks.size()); 299 300 // get the labels 301 // will look like "Version from 2013-00-0T00:00:00.000Z" 302 // and will sort chronologically based on a String comparison 303 final List<DomText> labels = 304 castList(versions 305 .getByXPath("//a[@class='version_link']/text()")); 306 final boolean chronological = labels.get(0).asText().compareTo(labels.get(1).toString()) < 0; 307 logger.debug("Versions {} in chronological order: {}, {}", 308 chronological ? "are" : "are not", labels.get(0).asText(), labels.get(1).asText()); 309 310 final HtmlPage firstRevision = 311 javascriptlessWebClient.getPage(versionLinks.get(chronological ? 0 : 1) 312 .getNodeValue()); 313 final List<DomText> v1Titles = 314 castList(firstRevision 315 .getByXPath("//span[@property='http://purl.org/dc/elements/1.1/title']/text()")); 316 final HtmlPage secondRevision = 317 javascriptlessWebClient.getPage(versionLinks.get(chronological ? 1 : 0) 318 .getNodeValue()); 319 final List<DomText> v2Titles = 320 castList(secondRevision 321 .getByXPath("//span[@property='http://purl.org/dc/elements/1.1/title']/text()")); 322 323 assertEquals("Version one should have one title.", 1, v1Titles.size()); 324 assertEquals("Version two should have one title.", 1, v2Titles.size()); 325 assertNotEquals("Each version should have a different title.", v1Titles.get(0), v2Titles.get(0)); 326 assertEquals("First version should be preserved.", "Object Title", v1Titles.get(0).getWholeText()); 327 assertEquals("Second version should be preserved.", "Updated Title", v2Titles.get(0).getWholeText()); 328 } 329 330 private static void postSparqlUpdateUsingHttpClient(final String sparql, final String pid) throws IOException { 331 final HttpPatch method = new HttpPatch(serverAddress + pid); 332 method.addHeader(CONTENT_TYPE, "application/sparql-update"); 333 final BasicHttpEntity entity = new BasicHttpEntity(); 334 entity.setContent(new ByteArrayInputStream(sparql.getBytes())); 335 method.setEntity(entity); 336 final HttpResponse response = client.execute(method); 337 assertEquals("Expected successful response.", 204, 338 response.getStatusLine().getStatusCode()); 339 } 340 341 @Test 342 @Ignore 343 public void testCreateNewObjectAndSetProperties() throws IOException { 344 final String pid = createNewObject(); 345 346 final HtmlPage page = javascriptlessWebClient.getPage(serverAddress + pid); 347 final HtmlForm form = (HtmlForm)page.getElementById("action_sparql_update"); 348 final HtmlTextArea sparql_update_query = (HtmlTextArea)page.getElementById("sparql_update_query"); 349 sparql_update_query.setText("INSERT { <> <info:some-predicate> 'asdf' } WHERE { }"); 350 351 final HtmlButton button = form.getFirstByXPath("button"); 352 button.click(); 353 354 final HtmlPage page1 = javascriptlessWebClient.getPage(serverAddress + pid); 355 assertTrue(page1.getElementById("metadata").asText().contains("some-predicate")); 356 } 357 358 @Test 359 @Ignore("htmlunit can't see links in the HTML5 <nav> element..") 360 public void testSparqlSearch() throws IOException { 361 final HtmlPage page = webClient.getPage(serverAddress); 362 363 logger.error(page.toString()); 364 page.getAnchorByText("Search").click(); 365 366 final HtmlForm form = (HtmlForm)page.getElementById("action_sparql_select"); 367 final HtmlTextArea q = form.getTextAreaByName("q"); 368 q.setText("SELECT ?subject WHERE { ?subject a <" + REPOSITORY_NAMESPACE + "Resource> }"); 369 final HtmlButton button = form.getFirstByXPath("button"); 370 button.click(); 371 } 372 373 374 private static void checkForHeaderBranding(final HtmlPage page) { 375 assertNotNull( 376 page.getFirstByXPath("//nav[@role='navigation']/div[@class='navbar-header']/a[@class='navbar-brand']")); 377 } 378 379 private String createNewObject() throws IOException { 380 381 final String pid = randomUUID().toString(); 382 383 final HtmlPage page = webClient.getPage(serverAddress); 384 final HtmlForm form = page.getFormByName("action_create"); 385 386 final HtmlInput slug = form.getInputByName("slug"); 387 slug.setValueAttribute(pid); 388 389 final HtmlButton button = form.getFirstByXPath("button"); 390 button.click(); 391 392 webClient.waitForBackgroundJavaScript(1000); 393 webClient.waitForBackgroundJavaScriptStartingBefore(10000); 394 395 396 return pid; 397 } 398 399 private CredentialsProvider getFedoraAdminCredentials() { 400 final CredentialsProvider credentials = new DefaultCredentialsProvider(); 401 credentials.setCredentials(AuthScope.ANY, FEDORA_ADMIN_CREDENTIALS); 402 return credentials; 403 } 404 405 private WebClient getDefaultWebClient() { 406 407 final WebClient webClient = new WebClient(FIREFOX_60); 408 webClient.addRequestHeader(ACCEPT, "text/html"); 409 webClient.setCredentialsProvider(getFedoraAdminCredentials()); 410 webClient.waitForBackgroundJavaScript(1000); 411 webClient.waitForBackgroundJavaScriptStartingBefore(10000); 412 webClient.setAjaxController(new NicelyResynchronizingAjaxController()); 413 //Suppress warning from IncorrectnessListener 414 webClient.setIncorrectnessListener(new SuppressWarningIncorrectnessListener()); 415 416 //Suppress css warning with the silent error handler. 417 webClient.setCssErrorHandler(new SilentCssErrorHandler()); 418 return webClient; 419 420 } 421 422 @SuppressWarnings("unchecked") 423 private static <T> List<T> castList(final List<?> l) { 424 return transform(l, x -> (T) x); 425 } 426 427 private static class SuppressWarningIncorrectnessListener 428 implements IncorrectnessListener { 429 @Override 430 public void notify(final String arg0, final Object arg1) { 431 432 } 433 } 434 435}