/**
* Copyright (c) Codice Foundation
* <p>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package ddf.test.itests.catalog;
import static org.codice.ddf.itests.common.csw.CswTestCommons.getCswInsertRequest;
import static org.codice.ddf.itests.common.csw.CswTestCommons.getCswRegistryStoreProperties;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.xml.HasXPath.hasXPath;
import static org.junit.Assert.fail;
import static com.jayway.restassured.RestAssured.given;
import static com.jayway.restassured.RestAssured.when;
import static com.xebialabs.restito.semantics.Action.bytesContent;
import static com.xebialabs.restito.semantics.Action.contentType;
import static com.xebialabs.restito.semantics.Action.ok;
import static com.xebialabs.restito.semantics.Condition.not;
import static com.xebialabs.restito.semantics.Condition.post;
import static com.xebialabs.restito.semantics.Condition.withPostBodyContaining;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.io.IOUtils;
import org.codice.ddf.itests.common.AbstractIntegrationTest;
import org.codice.ddf.itests.common.annotations.BeforeExam;
import org.codice.ddf.itests.common.annotations.ConditionalIgnoreRule;
import org.codice.ddf.itests.common.csw.mock.FederatedCswMockServer;
import org.codice.ddf.itests.common.utils.LoggingUtils;
import org.codice.ddf.registry.common.RegistryConstants;
import org.codice.ddf.registry.federationadmin.service.internal.FederationAdminService;
import org.codice.ddf.security.common.Security;
import org.hamcrest.CoreMatchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
import org.ops4j.pax.exam.spi.reactors.PerSuite;
import org.xml.sax.InputSource;
import com.google.common.collect.ImmutableMap;
import com.jayway.restassured.response.Response;
import com.jayway.restassured.response.ValidatableResponse;
@RunWith(PaxExam.class)
@ExamReactorStrategy(PerSuite.class)
public class TestRegistry extends AbstractIntegrationTest {
public static final String FACTORY_PID = "Csw_Registry_Store";
private static final String CATALOG_REGISTRY = "registry-app";
private static final String CATALOG_REGISTRY_CORE = "registry-core";
private static final String REGISTRY_CATALOG_STORE_ID = "cswRegistryCatalogStore";
private static final String ADMIN = "admin";
private static final String CSW_REGISTRY_TYPE = "CSW Registry Store";
private static final DynamicPort CSW_STUB_SERVER_PORT = new DynamicPort(8);
private static final String REMOTE_REGISTRY_ID = "urn:uuid:12121212121212121212121212121212";
private static final String METACARD_ID = "12312312312312312312312312312312";
private static final String REMOTE_METACARD_ID = "09876543210987654321098765432100";
private static final long SLEEP_TIME = 2000;
private static final Security SECURITY = Security.getInstance();
private static String storeId;
private static FederatedCswMockServer cswServer;
@Rule
public ConditionalIgnoreRule rule = new ConditionalIgnoreRule();
private Set<String> destinations;
public static String getCswRegistryInsert(String id, String regId) throws IOException {
return getCswInsertRequest("rim:RegistryPackage", getRegistryNode(id, regId, regId));
}
public static String getCswRegistryUpdate(String id, String nodeName, String date, String uuid)
throws IOException {
return "<csw:Transaction\n" + " service=\"CSW\"\n" + " version=\"2.0.2\"\n"
+ " verboseResponse=\"true\"\n"
+ " xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\">\n"
+ " <csw:Update typeName=\"rim:RegistryPackage\">\n" + getRegistryNode(id,
uuid,
uuid,
nodeName,
date) + "\n" + " </csw:Update>\n" + "</csw:Transaction>";
}
public static String getRegistryNode(String id, String regId, String remoteRegId)
throws IOException {
return getRegistryNode(id, regId, remoteRegId, "Node Name", "2016-01-26T17:16:34.996Z");
}
public static String getRegistryNode(String mcardId, String regId, String remoteReg,
String nodeName, String date) throws IOException {
return getFileContent("csw-rim-node.xml",
ImmutableMap.of("mcardId",
mcardId,
"nodeName",
nodeName,
"lastUpdated",
date,
"regId",
regId,
"remoteReg",
remoteReg));
}
public static String getRegistryQueryResponse(String mcardId, String regId, String remoteReg,
String nodeName, String date) throws IOException {
return getFileContent("default-csw-registry-query-response.xml",
ImmutableMap.of("mcardId",
mcardId,
"nodeName",
nodeName,
"lastUpdated",
date,
"regId",
regId,
"remoteReg",
remoteReg));
}
public static String getRegistryInsertResponse(String mcardId, String mcardTitle)
throws Exception {
return getFileContent("registry-csw-mock-insert-transaction-response.xml",
ImmutableMap.of("mcardId", mcardId, "metacardTitle", mcardTitle));
}
public static String getCswQueryEmptyResponse() throws Exception {
return "<?xml version='1.0' encoding='UTF-8'?>"
+ "<csw:GetRecordsResponse xmlns:dct=\"http://purl.org/dc/terms/\" xmlns:xml=\"http://www.w3.org/XML/1998/namespace\" xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\" xmlns:ows=\"http://www.opengis.net/ows\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" version=\"2.0.2\">\n"
+ " <csw:SearchStatus timestamp=\"2017-01-17T09:34:28.597-07:00\"/>\n"
+ " <csw:SearchResults numberOfRecordsMatched=\"0\" numberOfRecordsReturned=\"0\" nextRecord=\"0\" recordSchema=\"urn:oasis:names:tc:ebxml-regrep:xsd:rim:3.0\" elementSet=\"full\">\n"
+ " </csw:SearchResults>\n" + "</csw:GetRecordsResponse>";
}
@BeforeExam
public void beforeExam() throws Exception {
try {
waitForSystemReady();
getServiceManager().startFeature(true, CATALOG_REGISTRY);
getServiceManager().waitForAllBundles();
getServiceManager().startFeature(true, CATALOG_REGISTRY_CORE);
getServiceManager().waitForAllBundles();
cswServer = new FederatedCswMockServer("MockCswServer",
"http://localhost:",
Integer.parseInt(CSW_STUB_SERVER_PORT.getPort()));
String defaultResponse = getRegistryQueryResponse("11111111111111111111111111111111",
REMOTE_REGISTRY_ID,
REMOTE_REGISTRY_ID,
"RemoteRegistry",
"2018-02-26T17:16:34.996Z");
cswServer.setupDefaultQueryResponseExpectation(defaultResponse);
cswServer.start();
waitForMockServer();
getServiceManager().createManagedService(FACTORY_PID,
getCswRegistryStoreProperties(REGISTRY_CATALOG_STORE_ID,
"http://localhost:" + CSW_STUB_SERVER_PORT.getPort() + "/services/csw",
getServiceManager()));
storeId = String.format("RemoteRegistry (localhost:%s) (%s)",
CSW_STUB_SERVER_PORT.getPort(),
CSW_REGISTRY_TYPE);
getCatalogBundle().waitForCatalogStore(storeId);
} catch (Exception e) {
LoggingUtils.failWithThrowableStacktrace(e, "Failed in @BeforeExam: ");
}
}
@Before
public void setup() throws Exception {
destinations = new HashSet<>();
destinations.add(storeId);
String defaultResponse = getRegistryQueryResponse("11111111111111111111111111111111",
REMOTE_REGISTRY_ID,
REMOTE_REGISTRY_ID,
"RemoteRegistry",
"2018-02-26T17:16:34.996Z");
cswServer.setupDefaultQueryResponseExpectation(defaultResponse);
cswServer.reset();
waitForMockServer();
getCatalogBundle().waitForCatalogStore(storeId);
}
@After
public void tearDown() throws Exception {
cswServer.stop();
clearCatalog();
}
@Test
public void testCswRegistryIngest() throws Exception {
createRegistryEntry("2014ca7f59ac46f495e32b4a67a51279",
"urn:uuid:2014ca7f59ac46f495e32b4a67a51279");
}
@Test
public void testCswRegistryUpdate() throws Exception {
String regID = "urn:uuid:2014ca7f59ac46f495e32b4a67a51285";
String mcardId = "2014ca7f59ac46f495e32b4a67a51285";
String id = createRegistryEntry(mcardId, regID);
Response response = given().auth()
.preemptive()
.basic(ADMIN, ADMIN)
.body(getCswRegistryUpdate(id, "New Node Name", "2018-02-26T17:16:34.996Z", regID))
.header("Content-Type", "text/xml")
.expect()
.log()
.all()
.statusCode(200)
.when()
.post(CSW_PATH.getUrl());
ValidatableResponse validatableResponse = response.then();
validatableResponse.body(hasXPath("//TransactionResponse/TransactionSummary/totalInserted",
CoreMatchers.is("0")),
hasXPath("//TransactionResponse/TransactionSummary/totalUpdated",
CoreMatchers.is("1")),
hasXPath("//TransactionResponse/TransactionSummary/totalDeleted",
CoreMatchers.is("0")));
}
@Test
public void testCswRegistryUpdateFailure() throws Exception {
String regID = "urn:uuid:2014ca7f59ac46f495e32b4a67a51280";
String mcardId = "2014ca7f59ac46f495e32b4a67a51280";
String id = createRegistryEntry(mcardId, regID);
given().auth()
.preemptive()
.basic(ADMIN, ADMIN)
.body(getCswRegistryUpdate(regID, "New Node Name", "2014-02-26T17:16:34.996Z", id))
.log()
.all()
.header("Content-Type", "text/xml")
.when()
.post(CSW_PATH.getUrl())
.then()
.log()
.all()
.assertThat()
.statusCode(400);
}
@Test
public void testCswRegistryDelete() throws Exception {
String regID = "urn:uuid:2014ca7f59ac46f495e32b4a67a51281";
String mcardId = "2014ca7f59ac46f495e32b4a67a51281";
createRegistryEntry(mcardId, regID);
Response response = given().auth()
.preemptive()
.basic(ADMIN, ADMIN)
.body(getFileContent(CSW_REQUEST_RESOURCE_PATH + "/CswRegistryDeleteRequest",
ImmutableMap.of("id", mcardId)))
.header("Content-Type", "text/xml")
.expect()
.log()
.all()
.statusCode(200)
.when()
.post(CSW_PATH.getUrl());
ValidatableResponse validatableResponse = response.then();
validatableResponse.body(hasXPath("//TransactionResponse/TransactionSummary/totalInserted",
CoreMatchers.is("0")),
hasXPath("//TransactionResponse/TransactionSummary/totalUpdated",
CoreMatchers.is("0")),
hasXPath("//TransactionResponse/TransactionSummary/totalDeleted",
CoreMatchers.is("1")));
}
@Test
public void testCswRegistryStoreCreate() throws Exception {
String regID = "urn:uuid:2014ca7f59ac46f495e32b4a67a51277";
cswServer.setupDefaultInsertTransactionResponseExpectation(getRegistryInsertResponse(
REMOTE_METACARD_ID,
"Node Name"));
cswServer.reset();
waitForMockServer();
getCatalogBundle().waitForCatalogStore(storeId);
cswServer.whenHttp()
.match(post("/services/csw"),
withPostBodyContaining("GetRecords"),
withPostBodyContaining(RegistryConstants.REGISTRY_TAG_INTERNAL),
withPostBodyContaining(METACARD_ID))
.then(ok(),
contentType("text/xml"),
bytesContent(getCswQueryEmptyResponse().getBytes()));
cswServer.whenHttp()
.match(post("/services/csw"),
withPostBodyContaining("GetRecords"),
withPostBodyContaining(REMOTE_METACARD_ID))
.then(ok(),
contentType("text/xml"),
bytesContent(getRegistryQueryResponse(REMOTE_METACARD_ID,
regID,
REMOTE_REGISTRY_ID,
"NodeName",
"2016-01-26T17:16:34.996Z").getBytes()));
try {
SECURITY.runAsAdminWithException(() -> {
String id = createRegistryStoreEntry(METACARD_ID, regID, regID);
LOGGER.info("Created remote metacard with ID: {}", id);
assertThat(id, is(regID));
cswServer.verifyHttp()
.times(1,
withPostBodyContaining("Transaction"),
withPostBodyContaining("Insert"),
withPostBodyContaining(regID));
return null;
});
} catch (Exception e) {
String message = "There was an error creating the remote registry metacard.";
LOGGER.error(message, e);
throw new Exception(message, e);
}
}
@Test
public void testCswRegistryStoreCreateWithExistingRemoteEntry() throws Exception {
String regID = "urn:uuid:2014ca7f59ac46f495e32b4a67a51277";
cswServer.whenHttp()
.match(post("/services/csw"),
withPostBodyContaining("GetRecords"),
withPostBodyContaining(RegistryConstants.REGISTRY_TAG_INTERNAL),
withPostBodyContaining(METACARD_ID))
.then(ok(),
contentType("text/xml"),
bytesContent(getRegistryQueryResponse(REMOTE_METACARD_ID,
regID,
REMOTE_REGISTRY_ID,
"NodeName",
"2016-01-26T17:16:34.996Z").getBytes()));
try {
SECURITY.runAsAdminWithException(() -> {
String id = createRegistryStoreEntry(METACARD_ID, regID, regID);
assertThat(id, is(regID));
cswServer.verifyHttp()
.times(0,
withPostBodyContaining("Transaction"),
withPostBodyContaining("Ingest"),
withPostBodyContaining(METACARD_ID));
return null;
});
} catch (Exception e) {
String message = "There was an error creating the remote registry metacard.";
LOGGER.error(message, e);
throw new Exception(message, e);
}
}
@Test
public void testCswRegistryStoreUpdate() throws Exception {
String regID = "urn:uuid:2014ca7f59ac46f495e32b4a67a51290";
String remoteMetacardResponse = getRegistryQueryResponse(REMOTE_METACARD_ID,
regID,
REMOTE_REGISTRY_ID,
"NodeName",
"2016-01-26T17:16:34.996Z");
String remoteUpdatedMetacardResponse = getRegistryQueryResponse(REMOTE_METACARD_ID,
regID,
REMOTE_REGISTRY_ID,
"New Node Name",
"2016-01-26T17:16:34.996Z");
cswServer.whenHttp()
.match(post("/services/csw"),
withPostBodyContaining("GetRecords"),
withPostBodyContaining(RegistryConstants.REGISTRY_TAG_INTERNAL),
withPostBodyContaining(METACARD_ID))
.then(ok(),
contentType("text/xml"),
bytesContent(remoteMetacardResponse.getBytes()));
cswServer.whenHttp()
.match(post("/services/csw"),
withPostBodyContaining("GetRecords"),
not(withPostBodyContaining(RegistryConstants.REGISTRY_TAG_INTERNAL)),
withPostBodyContaining(METACARD_ID))
.then(ok(),
contentType("text/xml"),
bytesContent(remoteMetacardResponse.getBytes()));
cswServer.whenHttp()
.match(post("/services/csw"),
withPostBodyContaining("GetRecords"),
not(withPostBodyContaining(RegistryConstants.REGISTRY_TAG_INTERNAL)),
withPostBodyContaining(REMOTE_METACARD_ID))
.then(ok(),
contentType("text/xml"),
bytesContent(remoteUpdatedMetacardResponse.getBytes()));
try {
SECURITY.runAsAdminWithException(() -> {
FederationAdminService federationAdminServiceImpl = getServiceManager().getService(
FederationAdminService.class);
federationAdminServiceImpl.updateRegistryEntry(getRegistryNode(METACARD_ID,
regID,
regID,
"New Node Name",
"2016-02-26T17:16:34.996Z"), new HashSet(destinations));
cswServer.verifyHttp()
.times(1,
withPostBodyContaining("Transaction"),
withPostBodyContaining("Update"));
cswServer.verifyHttp()
.atLeast(4);
return null;
});
} catch (Exception e) {
String message = "There was an error updating the remote registry metacard.";
LOGGER.error(message, e);
throw new Exception(message, e);
}
}
@Test
public void testCswRegistryStoreDelete() throws Exception {
String regID = "urn:uuid:2014ca7f59ac46f495e32b4a67a51291";
String remoteMetacardResponse = getRegistryQueryResponse(REMOTE_METACARD_ID,
regID,
REMOTE_REGISTRY_ID,
"NodeName",
"2016-01-26T17:16:34.996Z");
try {
createRegistryEntry(METACARD_ID, regID);
cswServer.whenHttp()
.match(post("/services/csw"),
withPostBodyContaining("GetRecords"),
withPostBodyContaining(RegistryConstants.REGISTRY_TAG_INTERNAL))
.then(ok(),
contentType("text/xml"),
bytesContent(remoteMetacardResponse.getBytes()));
cswServer.whenHttp()
.match(post("/services/csw"),
withPostBodyContaining("GetRecords"),
not(withPostBodyContaining(RegistryConstants.REGISTRY_TAG_INTERNAL)),
withPostBodyContaining(REMOTE_METACARD_ID))
.then(ok(),
contentType("text/xml"),
bytesContent(remoteMetacardResponse.getBytes()));
SECURITY.runAsAdminWithException(() -> {
FederationAdminService federationAdminServiceImpl = getServiceManager().getService(
FederationAdminService.class);
List<String> toBeDeletedIDs = new ArrayList<>();
toBeDeletedIDs.add(regID);
federationAdminServiceImpl.deleteRegistryEntriesByRegistryIds(toBeDeletedIDs,
new HashSet(destinations));
cswServer.verifyHttp()
.times(1,
withPostBodyContaining("Transaction"),
withPostBodyContaining("Delete"));
cswServer.verifyHttp()
.atLeast(3);
return null;
});
} catch (Exception e) {
String message = "There was an error deleting the remote registry metacard.";
LOGGER.error(message, e);
throw new Exception(message, e);
}
}
@Test
public void testRestEndpoint() throws Exception {
final String regId = "urn:uuid:2014ca7f59ac46f495e32b4a67a51292";
final String mcardId = "2014ca7f59ac46f495e32b4a67a51292";
createRegistryEntry(mcardId, regId);
final String restUrl = SERVICE_ROOT.getUrl() + "/internal/registries/" + regId + "/report";
ValidatableResponse response = when().get(restUrl)
.then()
.log()
.all()
.assertThat()
.contentType("text/html");
final String xPathServices = "//html/body/h4";
response.body(hasXPath(xPathServices, CoreMatchers.is("CSW Federation Method")),
hasXPath(xPathServices + "[2]", CoreMatchers.is("Soap Federation Method")));
}
private String createRegistryEntry(String id, String regId) throws Exception {
Response response = given().auth()
.preemptive()
.basic(ADMIN, ADMIN)
.body(getCswRegistryInsert(id, regId))
.header("Content-Type", "text/xml")
.expect()
.log()
.all()
.statusCode(200)
.when()
.post(CSW_PATH.getUrl());
ValidatableResponse validatableResponse = response.then();
validatableResponse.body(hasXPath("//TransactionResponse/TransactionSummary/totalInserted",
CoreMatchers.is("1")),
hasXPath("//TransactionResponse/TransactionSummary/totalUpdated",
CoreMatchers.is("0")),
hasXPath("//TransactionResponse/TransactionSummary/totalDeleted",
CoreMatchers.is("0")));
XPath xPath = XPathFactory.newInstance()
.newXPath();
String idPath = "//*[local-name()='identifier']/text()";
InputSource xml = new InputSource(IOUtils.toInputStream(response.getBody()
.asString(), StandardCharsets.UTF_8.name()));
String mcardId = xPath.compile(idPath)
.evaluate(xml);
boolean foundMetacard = false;
long startTime = System.currentTimeMillis();
long metacardLookupTimeout = TimeUnit.MINUTES.toMillis(20);
while (!foundMetacard) {
LOGGER.info("Waiting for metacard to be created");
List entries = SECURITY.runAsAdminWithException(() -> {
FederationAdminService federationAdminServiceImpl = getServiceManager().getService(
FederationAdminService.class);
return federationAdminServiceImpl.getRegistryMetacardsByRegistryIds(Collections.singletonList(
regId));
});
if (!entries.isEmpty()) {
foundMetacard = true;
} else if (System.currentTimeMillis() - startTime > metacardLookupTimeout) {
fail("Registry Metacard was not created in the allowed time");
}
Thread.sleep(SLEEP_TIME);
}
return mcardId;
}
private String createRegistryStoreEntry(String id, String regId, String remoteRegId)
throws Exception {
FederationAdminService federationAdminServiceImpl = getServiceManager().getService(
FederationAdminService.class);
return federationAdminServiceImpl.addRegistryEntry(getRegistryNode(id, regId, remoteRegId),
new HashSet(destinations));
}
private void waitForMockServer() throws Exception {
long startTime = System.currentTimeMillis();
long timeout = TimeUnit.MINUTES.toMillis(2);
boolean available = false;
LOGGER.info("Waiting for Csw Mock Server to come up");
while (!available) {
try {
given().auth()
.preemptive()
.basic(ADMIN, ADMIN)
.body("<csw:GetCapabilities service=\"CSW\" xmlns:csw=\"http://www.opengis.net/cat/csw\" xmlns:ows=\"http://www.opengis.net/ows\"/>")
.header("Content-Type", "text/xml")
.expect()
.log()
.all()
.statusCode(200)
.when()
.post(CSW_PATH.getUrl());
available = true;
LOGGER.info("Csw Mock Server is running");
} catch (Error e) {
if (System.currentTimeMillis() - startTime > timeout) {
fail("Csw mock server didn't come up within allotted time.");
}
Thread.sleep(SLEEP_TIME);
}
}
}
}