/*
* Licensed to DuraSpace under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* DuraSpace licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fcrepo.integration;
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE;
import static javax.ws.rs.core.HttpHeaders.ACCEPT;
import static com.gargoylesoftware.htmlunit.BrowserVersion.FIREFOX_24;
import static com.google.common.collect.Lists.transform;
import static java.util.UUID.randomUUID;
import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_CONFIG_NAMESPACE;
import static org.fcrepo.kernel.api.RdfLexicon.REPOSITORY_NAMESPACE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.entity.BasicHttpEntity;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.IncorrectnessListener;
import com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.SilentCssErrorHandler;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.DomAttr;
import com.gargoylesoftware.htmlunit.html.DomText;
import com.gargoylesoftware.htmlunit.html.HtmlButton;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlFileInput;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlSelect;
import com.gargoylesoftware.htmlunit.html.HtmlTextArea;
/**
* <p>FedoraHtmlResponsesIT class.</p>
*
* @author cbeer
*/
public class FedoraHtmlResponsesIT extends AbstractResourceIT {
private WebClient webClient;
private WebClient javascriptlessWebClient;
@Before
public void setUp() {
webClient = getDefaultWebClient();
javascriptlessWebClient = getDefaultWebClient();
javascriptlessWebClient.getOptions().setJavaScriptEnabled(false);
}
@After
public void cleanUp() {
webClient.closeAllWindows();
javascriptlessWebClient.closeAllWindows();
}
@Test
public void testDescribeHtml() throws IOException {
final HtmlPage page = webClient.getPage(serverAddress);
((HtmlElement)page.getFirstByXPath("//h4[text()='Properties']")).click();
checkForHeaderBranding(page);
final String namespaceLabel = page
.getFirstByXPath("//span[@title='" + REPOSITORY_NAMESPACE + "']/text()")
.toString();
assertEquals("Expected to find namespace URIs displayed as their prefixes", "fedora:",
namespaceLabel);
}
@Test
public void testCreateNewNodeWithProvidedId() throws IOException {
createAndVerifyObjectWithIdFromRootPage(randomUUID().toString());
}
private HtmlPage createAndVerifyObjectWithIdFromRootPage(final String pid) throws IOException {
final HtmlPage page = webClient.getPage(serverAddress);
final HtmlForm form = (HtmlForm)page.getElementById("action_create");
final HtmlSelect type = (HtmlSelect)page.getElementById("new_mixin");
type.getOptionByValue("container").setSelected(true);
final HtmlInput new_id = (HtmlInput)page.getElementById("new_id");
new_id.setValueAttribute(pid);
final HtmlButton button = form.getFirstByXPath("button");
button.click();
try {
final HtmlPage page1 = webClient.getPage(serverAddress + pid);
assertEquals("Page had wrong title!", serverAddress + pid, page1.getTitleText());
return page1;
} catch (final FailingHttpStatusCodeException e) {
fail("Did not successfully retrieve created page! Got HTTP code: " + e.getStatusCode());
return null;
}
}
@Test
public void testCreateNewNodeWithGeneratedId() throws IOException {
final HtmlPage page = webClient.getPage(serverAddress);
final HtmlForm form = (HtmlForm)page.getElementById("action_create");
final HtmlSelect type = (HtmlSelect)page.getElementById("new_mixin");
type.getOptionByValue("container").setSelected(true);
final HtmlButton button = form.getFirstByXPath("button");
button.click();
final HtmlPage page1 = javascriptlessWebClient.getPage(serverAddress);
assertTrue("Didn't see new information in page!", !page1.asText().equals(page.asText()));
}
@Test
@Ignore("The htmlunit web client can't handle the HTML5 file API")
public void testCreateNewDatastream() throws IOException {
final String pid = randomUUID().toString();
// can't do this with javascript, because HTMLUnit doesn't speak the HTML5 file api
final HtmlPage page = webClient.getPage(serverAddress);
final HtmlForm form = (HtmlForm)page.getElementById("action_create");
final HtmlInput slug = form.getInputByName("slug");
slug.setValueAttribute(pid);
final HtmlSelect type = (HtmlSelect)page.getElementById("new_mixin");
type.getOptionByValue("binary").setSelected(true);
final HtmlFileInput fileInput = (HtmlFileInput)page.getElementById("datastream_payload");
fileInput.setData("abcdef".getBytes());
fileInput.setContentType("application/pdf");
final HtmlButton button = form.getFirstByXPath("button");
button.click();
final HtmlPage page1 = javascriptlessWebClient.getPage(serverAddress + pid);
assertEquals(serverAddress + pid, page1.getTitleText());
}
@Test
public void testCreateNewDatastreamWithNoFileAttached() throws IOException {
final String pid = randomUUID().toString();
// can't do this with javascript, because HTMLUnit doesn't speak the HTML5 file api
final HtmlPage page = webClient.getPage(serverAddress);
final HtmlForm form = (HtmlForm)page.getElementById("action_create");
final HtmlInput slug = form.getInputByName("slug");
slug.setValueAttribute(pid);
final HtmlSelect type = (HtmlSelect)page.getElementById("new_mixin");
type.getOptionByValue("binary").setSelected(true);
final HtmlButton button = form.getFirstByXPath("button");
button.click();
javascriptlessWebClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
final int status = javascriptlessWebClient.getPage(serverAddress + pid).getWebResponse().getStatusCode();
assertEquals(NOT_FOUND.getStatusCode(), status);
}
@Test
public void testCreateNewObjectAndDeleteIt() throws IOException {
final boolean throwExceptionOnFailingStatusCode = webClient.getOptions().isThrowExceptionOnFailingStatusCode();
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
final String pid = createNewObject();
final HtmlPage page = webClient.getPage(serverAddress + pid);
final HtmlForm action_delete = page.getFormByName("action_delete");
action_delete.getButtonByName("delete-button").click();
webClient.waitForBackgroundJavaScript(1000);
webClient.waitForBackgroundJavaScriptStartingBefore(10000);
final Page page2 = webClient.getPage(serverAddress + pid);
assertEquals("Didn't get a 410!", 410, page2.getWebResponse()
.getStatusCode());
webClient.getOptions().setThrowExceptionOnFailingStatusCode(throwExceptionOnFailingStatusCode);
}
/**
* This test walks through the steps for creating an object, setting some
* metadata, creating a version, updating that metadata, viewing the
* version history to find that old version.
*
* @throws IOException exception thrown during this function
*/
@Ignore
@Test
public void testVersionCreationAndNavigation() throws IOException {
final String pid = randomUUID().toString();
createAndVerifyObjectWithIdFromRootPage(pid);
final String updateSparql = "PREFIX dc: <http://purl.org/dc/elements/1.1/>\n" +
"PREFIX fedora: <" + REPOSITORY_NAMESPACE + ">\n" +
"\n" +
"INSERT DATA { <> config:versioningPolicy \"auto-version\" ; dc:title \"Object Title\". }";
postSparqlUpdateUsingHttpClient(updateSparql, pid);
final HtmlPage objectPage = javascriptlessWebClient.getPage(serverAddress + pid);
assertEquals("Auto versioning should be set.", "auto-version",
objectPage.getFirstByXPath(
"//span[@property='" + FEDORA_CONFIG_NAMESPACE + "versioningPolicy']/text()")
.toString());
assertEquals("Title should be set.", "Object Title",
objectPage.getFirstByXPath("//span[@property='http://purl.org/dc/elements/1.1/title']/text()")
.toString());
final String updateSparql2 = "PREFIX dc: <http://purl.org/dc/elements/1.1/>\n" +
"\n" +
"DELETE { <> dc:title ?t }\n" +
"INSERT { <> dc:title \"Updated Title\" }" +
"WHERE { <> dc:title ?t }";
postSparqlUpdateUsingHttpClient(updateSparql2, pid);
final HtmlPage versions =
javascriptlessWebClient.getPage(serverAddress + pid + "/fcr:versions");
final List<DomAttr> versionLinks =
castList(versions.getByXPath("//a[@class='version_link']/@href"));
assertEquals("There should be two revisions.", 2, versionLinks.size());
// get the labels
// will look like "Version from 2013-00-0T00:00:00.000Z"
// and will sort chronologically based on a String comparison
final List<DomText> labels =
castList(versions
.getByXPath("//a[@class='version_link']/text()"));
final boolean chronological = labels.get(0).asText().compareTo(labels.get(1).toString()) < 0;
logger.debug("Versions {} in chronological order: {}, {}",
chronological ? "are" : "are not", labels.get(0).asText(), labels.get(1).asText());
final HtmlPage firstRevision =
javascriptlessWebClient.getPage(versionLinks.get(chronological ? 0 : 1)
.getNodeValue());
final List<DomText> v1Titles =
castList(firstRevision
.getByXPath("//span[@property='http://purl.org/dc/elements/1.1/title']/text()"));
final HtmlPage secondRevision =
javascriptlessWebClient.getPage(versionLinks.get(chronological ? 1 : 0)
.getNodeValue());
final List<DomText> v2Titles =
castList(secondRevision
.getByXPath("//span[@property='http://purl.org/dc/elements/1.1/title']/text()"));
assertEquals("Version one should have one title.", 1, v1Titles.size());
assertEquals("Version two should have one title.", 1, v2Titles.size());
assertNotEquals("Each version should have a different title.", v1Titles.get(0), v2Titles.get(0));
assertEquals("First version should be preserved.", "Object Title", v1Titles.get(0).getWholeText());
assertEquals("Second version should be preserved.", "Updated Title", v2Titles.get(0).getWholeText());
}
private static void postSparqlUpdateUsingHttpClient(final String sparql, final String pid) throws IOException {
final HttpPatch method = new HttpPatch(serverAddress + pid);
method.addHeader(CONTENT_TYPE, "application/sparql-update");
final BasicHttpEntity entity = new BasicHttpEntity();
entity.setContent(new ByteArrayInputStream(sparql.getBytes()));
method.setEntity(entity);
final HttpResponse response = client.execute(method);
assertEquals("Expected successful response.", 204,
response.getStatusLine().getStatusCode());
}
@Test
@Ignore
public void testCreateNewObjectAndSetProperties() throws IOException {
final String pid = createNewObject();
final HtmlPage page = javascriptlessWebClient.getPage(serverAddress + pid);
final HtmlForm form = (HtmlForm)page.getElementById("action_sparql_update");
final HtmlTextArea sparql_update_query = (HtmlTextArea)page.getElementById("sparql_update_query");
sparql_update_query.setText("INSERT { <> <info:some-predicate> 'asdf' } WHERE { }");
final HtmlButton button = form.getFirstByXPath("button");
button.click();
final HtmlPage page1 = javascriptlessWebClient.getPage(serverAddress + pid);
assertTrue(page1.getElementById("metadata").asText().contains("some-predicate"));
}
@Test
@Ignore("htmlunit can't see links in the HTML5 <nav> element..")
public void testSparqlSearch() throws IOException {
final HtmlPage page = webClient.getPage(serverAddress);
logger.error(page.toString());
page.getAnchorByText("Search").click();
final HtmlForm form = (HtmlForm)page.getElementById("action_sparql_select");
final HtmlTextArea q = form.getTextAreaByName("q");
q.setText("SELECT ?subject WHERE { ?subject a <" + REPOSITORY_NAMESPACE + "Resource> }");
final HtmlButton button = form.getFirstByXPath("button");
button.click();
}
private static void checkForHeaderBranding(final HtmlPage page) {
assertNotNull(
page.getFirstByXPath("//nav[@role='navigation']/div[@class='navbar-header']/a[@class='navbar-brand']"));
}
private String createNewObject() throws IOException {
final String pid = randomUUID().toString();
final HtmlPage page = webClient.getPage(serverAddress);
final HtmlForm form = page.getFormByName("action_create");
final HtmlInput slug = form.getInputByName("slug");
slug.setValueAttribute(pid);
final HtmlButton button = form.getFirstByXPath("button");
button.click();
webClient.waitForBackgroundJavaScript(1000);
webClient.waitForBackgroundJavaScriptStartingBefore(10000);
return pid;
}
private WebClient getDefaultWebClient() {
final WebClient webClient = new WebClient(FIREFOX_24);
webClient.addRequestHeader(ACCEPT, "text/html");
webClient.waitForBackgroundJavaScript(1000);
webClient.waitForBackgroundJavaScriptStartingBefore(10000);
webClient.setAjaxController(new NicelyResynchronizingAjaxController());
//Suppress warning from IncorrectnessListener
webClient.setIncorrectnessListener(new SuppressWarningIncorrectnessListener());
//Suppress css warning with the silent error handler.
webClient.setCssErrorHandler(new SilentCssErrorHandler());
return webClient;
}
@SuppressWarnings("unchecked")
private static <T> List<T> castList(final List<?> l) {
return transform(l, x -> (T) x);
}
private static class SuppressWarningIncorrectnessListener
implements IncorrectnessListener {
@Override
public void notify(final String arg0, final Object arg1) {
}
}
}