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