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 javax.ws.rs.core.Response.Status.NOT_FOUND; 021import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; 022import static javax.ws.rs.core.HttpHeaders.ACCEPT; 023import static com.gargoylesoftware.htmlunit.BrowserVersion.FIREFOX_24; 024import static com.google.common.collect.Lists.transform; 025import static java.util.UUID.randomUUID; 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 org.apache.http.HttpResponse; 039import org.apache.http.client.methods.HttpPatch; 040import org.apache.http.entity.BasicHttpEntity; 041import org.junit.After; 042import org.junit.Before; 043import org.junit.Ignore; 044import org.junit.Test; 045import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; 046import com.gargoylesoftware.htmlunit.IncorrectnessListener; 047import com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController; 048import com.gargoylesoftware.htmlunit.Page; 049import com.gargoylesoftware.htmlunit.SilentCssErrorHandler; 050import com.gargoylesoftware.htmlunit.WebClient; 051import com.gargoylesoftware.htmlunit.html.DomAttr; 052import com.gargoylesoftware.htmlunit.html.DomText; 053import com.gargoylesoftware.htmlunit.html.HtmlButton; 054import com.gargoylesoftware.htmlunit.html.HtmlElement; 055import com.gargoylesoftware.htmlunit.html.HtmlFileInput; 056import com.gargoylesoftware.htmlunit.html.HtmlForm; 057import com.gargoylesoftware.htmlunit.html.HtmlInput; 058import com.gargoylesoftware.htmlunit.html.HtmlPage; 059import com.gargoylesoftware.htmlunit.html.HtmlSelect; 060import com.gargoylesoftware.htmlunit.html.HtmlTextArea; 061 062/** 063 * <p>FedoraHtmlResponsesIT class.</p> 064 * 065 * @author cbeer 066 */ 067public class FedoraHtmlResponsesIT extends AbstractResourceIT { 068 069 private WebClient webClient; 070 private WebClient javascriptlessWebClient; 071 072 @Before 073 public void setUp() { 074 webClient = getDefaultWebClient(); 075 076 javascriptlessWebClient = getDefaultWebClient(); 077 javascriptlessWebClient.getOptions().setJavaScriptEnabled(false); 078 } 079 080 @After 081 public void cleanUp() { 082 webClient.closeAllWindows(); 083 javascriptlessWebClient.closeAllWindows(); 084 } 085 086 @Test 087 public void testDescribeHtml() throws IOException { 088 final HtmlPage page = webClient.getPage(serverAddress); 089 ((HtmlElement)page.getFirstByXPath("//h4[text()='Properties']")).click(); 090 091 checkForHeaderBranding(page); 092 final String namespaceLabel = page 093 .getFirstByXPath("//span[@title='" + REPOSITORY_NAMESPACE + "']/text()") 094 .toString(); 095 096 assertEquals("Expected to find namespace URIs displayed as their prefixes", "fedora:", 097 namespaceLabel); 098 } 099 100 @Test 101 public void testCreateNewNodeWithProvidedId() throws IOException { 102 createAndVerifyObjectWithIdFromRootPage(randomUUID().toString()); 103 } 104 105 private HtmlPage createAndVerifyObjectWithIdFromRootPage(final String pid) throws IOException { 106 final HtmlPage page = webClient.getPage(serverAddress); 107 final HtmlForm form = (HtmlForm)page.getElementById("action_create"); 108 final HtmlSelect type = (HtmlSelect)page.getElementById("new_mixin"); 109 type.getOptionByValue("container").setSelected(true); 110 111 final HtmlInput new_id = (HtmlInput)page.getElementById("new_id"); 112 new_id.setValueAttribute(pid); 113 final HtmlButton button = form.getFirstByXPath("button"); 114 button.click(); 115 116 117 try { 118 final HtmlPage page1 = webClient.getPage(serverAddress + pid); 119 assertEquals("Page had wrong title!", serverAddress + pid, page1.getTitleText()); 120 return page1; 121 } catch (final FailingHttpStatusCodeException e) { 122 fail("Did not successfully retrieve created page! Got HTTP code: " + e.getStatusCode()); 123 return null; 124 } 125 } 126 127 @Test 128 public void testCreateNewNodeWithGeneratedId() throws IOException { 129 130 final HtmlPage page = webClient.getPage(serverAddress); 131 final HtmlForm form = (HtmlForm)page.getElementById("action_create"); 132 final HtmlSelect type = (HtmlSelect)page.getElementById("new_mixin"); 133 type.getOptionByValue("container").setSelected(true); 134 final HtmlButton button = form.getFirstByXPath("button"); 135 button.click(); 136 137 final HtmlPage page1 = javascriptlessWebClient.getPage(serverAddress); 138 assertTrue("Didn't see new information in page!", !page1.asText().equals(page.asText())); 139 } 140 141 @Test 142 @Ignore("The htmlunit web client can't handle the HTML5 file API") 143 public void testCreateNewDatastream() throws IOException { 144 145 final String pid = randomUUID().toString(); 146 147 // can't do this with javascript, because HTMLUnit doesn't speak the HTML5 file api 148 final HtmlPage page = webClient.getPage(serverAddress); 149 final HtmlForm form = (HtmlForm)page.getElementById("action_create"); 150 151 final HtmlInput slug = form.getInputByName("slug"); 152 slug.setValueAttribute(pid); 153 154 final HtmlSelect type = (HtmlSelect)page.getElementById("new_mixin"); 155 type.getOptionByValue("binary").setSelected(true); 156 157 final HtmlFileInput fileInput = (HtmlFileInput)page.getElementById("datastream_payload"); 158 fileInput.setData("abcdef".getBytes()); 159 fileInput.setContentType("application/pdf"); 160 161 final HtmlButton button = form.getFirstByXPath("button"); 162 button.click(); 163 164 final HtmlPage page1 = javascriptlessWebClient.getPage(serverAddress + pid); 165 assertEquals(serverAddress + pid, page1.getTitleText()); 166 } 167 168 @Test 169 public void testCreateNewDatastreamWithNoFileAttached() throws IOException { 170 171 final String pid = randomUUID().toString(); 172 173 // can't do this with javascript, because HTMLUnit doesn't speak the HTML5 file api 174 final HtmlPage page = webClient.getPage(serverAddress); 175 final HtmlForm form = (HtmlForm)page.getElementById("action_create"); 176 177 final HtmlInput slug = form.getInputByName("slug"); 178 slug.setValueAttribute(pid); 179 180 final HtmlSelect type = (HtmlSelect)page.getElementById("new_mixin"); 181 type.getOptionByValue("binary").setSelected(true); 182 183 final HtmlButton button = form.getFirstByXPath("button"); 184 button.click(); 185 186 javascriptlessWebClient.getOptions().setThrowExceptionOnFailingStatusCode(false); 187 final int status = javascriptlessWebClient.getPage(serverAddress + pid).getWebResponse().getStatusCode(); 188 assertEquals(NOT_FOUND.getStatusCode(), status); 189 } 190 191 @Test 192 public void testCreateNewObjectAndDeleteIt() throws IOException { 193 final boolean throwExceptionOnFailingStatusCode = webClient.getOptions().isThrowExceptionOnFailingStatusCode(); 194 webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); 195 196 final String pid = createNewObject(); 197 final HtmlPage page = webClient.getPage(serverAddress + pid); 198 final HtmlForm action_delete = page.getFormByName("action_delete"); 199 action_delete.getButtonByName("delete-button").click(); 200 webClient.waitForBackgroundJavaScript(1000); 201 webClient.waitForBackgroundJavaScriptStartingBefore(10000); 202 203 final Page page2 = webClient.getPage(serverAddress + pid); 204 assertEquals("Didn't get a 410!", 410, page2.getWebResponse() 205 .getStatusCode()); 206 207 webClient.getOptions().setThrowExceptionOnFailingStatusCode(throwExceptionOnFailingStatusCode); 208 } 209 210 /** 211 * This test walks through the steps for creating an object, setting some 212 * metadata, creating a version, updating that metadata, viewing the 213 * version history to find that old version. 214 * 215 * @throws IOException exception thrown during this function 216 */ 217 @Ignore 218 @Test 219 public void testVersionCreationAndNavigation() throws IOException { 220 final String pid = randomUUID().toString(); 221 createAndVerifyObjectWithIdFromRootPage(pid); 222 223 final String updateSparql = "PREFIX dc: <http://purl.org/dc/elements/1.1/>\n" + 224 "PREFIX fedora: <" + REPOSITORY_NAMESPACE + ">\n" + 225 "\n" + 226 "INSERT DATA { <> config:versioningPolicy \"auto-version\" ; dc:title \"Object Title\". }"; 227 postSparqlUpdateUsingHttpClient(updateSparql, pid); 228 229 final HtmlPage objectPage = javascriptlessWebClient.getPage(serverAddress + pid); 230 assertEquals("Auto versioning should be set.", "auto-version", 231 objectPage.getFirstByXPath( 232 "//span[@property='" + FEDORA_CONFIG_NAMESPACE + "versioningPolicy']/text()") 233 .toString()); 234 assertEquals("Title should be set.", "Object Title", 235 objectPage.getFirstByXPath("//span[@property='http://purl.org/dc/elements/1.1/title']/text()") 236 .toString()); 237 238 final String updateSparql2 = "PREFIX dc: <http://purl.org/dc/elements/1.1/>\n" + 239 "\n" + 240 "DELETE { <> dc:title ?t }\n" + 241 "INSERT { <> dc:title \"Updated Title\" }" + 242 "WHERE { <> dc:title ?t }"; 243 postSparqlUpdateUsingHttpClient(updateSparql2, pid); 244 245 final HtmlPage versions = 246 javascriptlessWebClient.getPage(serverAddress + pid + "/fcr:versions"); 247 final List<DomAttr> versionLinks = 248 castList(versions.getByXPath("//a[@class='version_link']/@href")); 249 assertEquals("There should be two revisions.", 2, versionLinks.size()); 250 251 // get the labels 252 // will look like "Version from 2013-00-0T00:00:00.000Z" 253 // and will sort chronologically based on a String comparison 254 final List<DomText> labels = 255 castList(versions 256 .getByXPath("//a[@class='version_link']/text()")); 257 final boolean chronological = labels.get(0).asText().compareTo(labels.get(1).toString()) < 0; 258 logger.debug("Versions {} in chronological order: {}, {}", 259 chronological ? "are" : "are not", labels.get(0).asText(), labels.get(1).asText()); 260 261 final HtmlPage firstRevision = 262 javascriptlessWebClient.getPage(versionLinks.get(chronological ? 0 : 1) 263 .getNodeValue()); 264 final List<DomText> v1Titles = 265 castList(firstRevision 266 .getByXPath("//span[@property='http://purl.org/dc/elements/1.1/title']/text()")); 267 final HtmlPage secondRevision = 268 javascriptlessWebClient.getPage(versionLinks.get(chronological ? 1 : 0) 269 .getNodeValue()); 270 final List<DomText> v2Titles = 271 castList(secondRevision 272 .getByXPath("//span[@property='http://purl.org/dc/elements/1.1/title']/text()")); 273 274 assertEquals("Version one should have one title.", 1, v1Titles.size()); 275 assertEquals("Version two should have one title.", 1, v2Titles.size()); 276 assertNotEquals("Each version should have a different title.", v1Titles.get(0), v2Titles.get(0)); 277 assertEquals("First version should be preserved.", "Object Title", v1Titles.get(0).getWholeText()); 278 assertEquals("Second version should be preserved.", "Updated Title", v2Titles.get(0).getWholeText()); 279 } 280 281 private static void postSparqlUpdateUsingHttpClient(final String sparql, final String pid) throws IOException { 282 final HttpPatch method = new HttpPatch(serverAddress + pid); 283 method.addHeader(CONTENT_TYPE, "application/sparql-update"); 284 final BasicHttpEntity entity = new BasicHttpEntity(); 285 entity.setContent(new ByteArrayInputStream(sparql.getBytes())); 286 method.setEntity(entity); 287 final HttpResponse response = client.execute(method); 288 assertEquals("Expected successful response.", 204, 289 response.getStatusLine().getStatusCode()); 290 } 291 292 @Test 293 @Ignore 294 public void testCreateNewObjectAndSetProperties() throws IOException { 295 final String pid = createNewObject(); 296 297 final HtmlPage page = javascriptlessWebClient.getPage(serverAddress + pid); 298 final HtmlForm form = (HtmlForm)page.getElementById("action_sparql_update"); 299 final HtmlTextArea sparql_update_query = (HtmlTextArea)page.getElementById("sparql_update_query"); 300 sparql_update_query.setText("INSERT { <> <info:some-predicate> 'asdf' } WHERE { }"); 301 302 final HtmlButton button = form.getFirstByXPath("button"); 303 button.click(); 304 305 final HtmlPage page1 = javascriptlessWebClient.getPage(serverAddress + pid); 306 assertTrue(page1.getElementById("metadata").asText().contains("some-predicate")); 307 } 308 309 @Test 310 @Ignore("htmlunit can't see links in the HTML5 <nav> element..") 311 public void testSparqlSearch() throws IOException { 312 final HtmlPage page = webClient.getPage(serverAddress); 313 314 logger.error(page.toString()); 315 page.getAnchorByText("Search").click(); 316 317 final HtmlForm form = (HtmlForm)page.getElementById("action_sparql_select"); 318 final HtmlTextArea q = form.getTextAreaByName("q"); 319 q.setText("SELECT ?subject WHERE { ?subject a <" + REPOSITORY_NAMESPACE + "Resource> }"); 320 final HtmlButton button = form.getFirstByXPath("button"); 321 button.click(); 322 } 323 324 325 private static void checkForHeaderBranding(final HtmlPage page) { 326 assertNotNull( 327 page.getFirstByXPath("//nav[@role='navigation']/div[@class='navbar-header']/a[@class='navbar-brand']")); 328 } 329 330 private String createNewObject() throws IOException { 331 332 final String pid = randomUUID().toString(); 333 334 final HtmlPage page = webClient.getPage(serverAddress); 335 final HtmlForm form = page.getFormByName("action_create"); 336 337 final HtmlInput slug = form.getInputByName("slug"); 338 slug.setValueAttribute(pid); 339 340 final HtmlButton button = form.getFirstByXPath("button"); 341 button.click(); 342 343 webClient.waitForBackgroundJavaScript(1000); 344 webClient.waitForBackgroundJavaScriptStartingBefore(10000); 345 346 347 return pid; 348 } 349 350 351 private WebClient getDefaultWebClient() { 352 353 final WebClient webClient = new WebClient(FIREFOX_24); 354 webClient.addRequestHeader(ACCEPT, "text/html"); 355 356 webClient.waitForBackgroundJavaScript(1000); 357 webClient.waitForBackgroundJavaScriptStartingBefore(10000); 358 webClient.setAjaxController(new NicelyResynchronizingAjaxController()); 359 //Suppress warning from IncorrectnessListener 360 webClient.setIncorrectnessListener(new SuppressWarningIncorrectnessListener()); 361 362 //Suppress css warning with the silent error handler. 363 webClient.setCssErrorHandler(new SilentCssErrorHandler()); 364 return webClient; 365 366 } 367 368 @SuppressWarnings("unchecked") 369 private static <T> List<T> castList(final List<?> l) { 370 return transform(l, x -> (T) x); 371 } 372 373 private static class SuppressWarningIncorrectnessListener 374 implements IncorrectnessListener { 375 @Override 376 public void notify(final String arg0, final Object arg1) { 377 378 } 379 } 380}