/* (c) 2016 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geogig.geoserver.rest; import static org.custommonkey.xmlunit.XMLAssert.assertXpathEvaluatesTo; import static org.custommonkey.xmlunit.XMLAssert.assertXpathExists; import static org.custommonkey.xmlunit.XMLAssert.assertXpathNotExists; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.locationtech.geogig.model.impl.RevObjectTestSupport.hashString; import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.List; import java.util.Random; import org.geogig.geoserver.GeoGigTestData; import org.geogig.geoserver.GeoGigTestData.CatalogBuilder; import org.geogig.geoserver.config.RepositoryInfo; import org.geogig.geoserver.config.RepositoryManager; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.DataStoreInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.data.test.SystemTestData; import org.geoserver.test.GeoServerSystemTestSupport; import org.geoserver.test.TestSetup; import org.geoserver.test.TestSetupFrequency; import org.geotools.data.DataAccess; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.locationtech.geogig.geotools.data.GeoGigDataStore; import org.locationtech.geogig.geotools.data.GeoGigDataStoreFactory; import org.locationtech.geogig.model.ObjectId; import org.locationtech.geogig.model.Ref; import org.locationtech.geogig.model.RevObject; import org.locationtech.geogig.plumbing.RefParse; import org.locationtech.geogig.plumbing.ResolveTreeish; import org.locationtech.geogig.plumbing.RevObjectParse; import org.locationtech.geogig.repository.RepositoryResolver; import org.locationtech.geogig.repository.impl.GeoGIG; import org.locationtech.geogig.storage.datastream.DataStreamSerializationFactoryV1; import org.locationtech.geogig.storage.impl.ObjectSerializingFactory; import org.opengis.feature.Feature; import org.opengis.feature.type.FeatureType; import org.restlet.data.MediaType; import org.springframework.mock.web.MockHttpServletResponse; import org.w3c.dom.Document; import com.google.common.base.Throwables; import com.google.common.collect.AbstractIterator; import com.google.common.collect.Lists; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; @TestSetup(run = TestSetupFrequency.REPEAT) public class GeoGigWebAPIIntegrationTest extends GeoServerSystemTestSupport { /** * {@code /geogig/repos/<repoId>} */ private String BASE_URL; private static final Random rnd = new Random(); @Rule public GeoGigTestData geogigData = new GeoGigTestData(); /** * Override to avoid creating default geoserver test data */ @Override protected void setUpTestData(SystemTestData testData) throws Exception { // do nothing } @Before public void before() throws Exception { // protected void onSetUp(SystemTestData testData) throws Exception { Catalog catalog = getCatalog(); geogigData.init()// .config("user.name", "gabriel")// .config("user.email", "gabriel@test.com")// .createTypeTree("lines", "geom:LineString:srid=4326")// .createTypeTree("points", "geom:Point:srid=4326")// .add()// .commit("created type trees")// .get(); geogigData.insert("points", // "p1=geom:POINT(0 0)", // "p2=geom:POINT(1 1)", // "p3=geom:POINT(2 2)"); geogigData.insert("lines", // "l1=geom:LINESTRING(-10 0, 10 0)", // "l2=geom:LINESTRING(0 0, 180 0)"); geogigData.add().commit("Added test features"); CatalogBuilder catalogBuilder = geogigData.newCatalogBuilder(catalog); int i = rnd.nextInt(); catalogBuilder.namespace("geogig.org/" + i).workspace("geogigws" + i) .store("geogigstore" + i); catalogBuilder.addAllRepoLayers().build(); String workspaceName = catalogBuilder.workspaceName(); String storeName = catalogBuilder.storeName(); String layerName = workspaceName + ":points"; LayerInfo pointLayerInfo = catalog.getLayerByName(layerName); assertNotNull(pointLayerInfo); layerName = workspaceName + ":lines"; LayerInfo lineLayerInfo = catalog.getLayerByName(layerName); assertNotNull(lineLayerInfo); DataStoreInfo dsInfo = catalog.getDataStoreByName(workspaceName, storeName); assertNotNull(dsInfo); assertEquals(GeoGigDataStoreFactory.DISPLAY_NAME, dsInfo.getType()); DataAccess<? extends FeatureType, ? extends Feature> dataStore = dsInfo.getDataStore(null); assertNotNull(dataStore); assertTrue(dataStore instanceof GeoGigDataStore); String repoStr = (String) dsInfo.getConnectionParameters() .get(GeoGigDataStoreFactory.REPOSITORY.key); // resolve the repo URI repoURI = new URI(repoStr); RepositoryResolver resolver = RepositoryResolver.lookup(repoURI); String repoName = resolver.getName(repoURI); RepositoryInfo repositoryInfo = RepositoryManager.get().getByRepoName(repoName); assertNotNull(repositoryInfo); BASE_URL = "/geogig/repos/testrepo"; } @After public void after() { RepositoryManager.close(); getCatalog().dispose(); } /** * Test for resource {@code /rest/<repository>/repo/manifest} */ @Test public void testGetManifest() throws Exception { final String url = BASE_URL + "/repo/manifest"; MockHttpServletResponse sr = getAsServletResponse(url); assertEquals(200, sr.getStatus()); String contentType = sr.getContentType(); assertTrue(contentType, sr.getContentType().startsWith("text/plain")); String responseBody = sr.getContentAsString(); assertNotNull(responseBody); assertTrue(responseBody, responseBody.startsWith("HEAD refs/heads/master")); } /** * Test for resource {@code /rest/<repository>/repo/exists?oid=...} */ @Test public void testRevObjectExists() throws Exception { final String resource = BASE_URL + "/repo/exists?oid="; GeoGIG geogig = geogigData.getGeogig(); Ref head = geogig.command(RefParse.class).setName(Ref.HEAD).call().get(); ObjectId commitId = head.getObjectId(); String url; url = resource + commitId.toString(); assertResponse(url, "1"); ObjectId treeId = geogig.command(ResolveTreeish.class).setTreeish(commitId).call().get(); url = resource + treeId.toString(); assertResponse(url, "1"); url = resource + hashString("fake"); assertResponse(url, "0"); } /** * Test for resource {@code /rest/<repository>/repo/objects/<oid>} */ @Test public void testGetObject() throws Exception { GeoGIG geogig = geogigData.getGeogig(); Ref head = geogig.command(RefParse.class).setName(Ref.HEAD).call().get(); ObjectId commitId = head.getObjectId(); ObjectId treeId = geogig.command(ResolveTreeish.class).setTreeish(commitId).call().get(); testGetRemoteObject(commitId); testGetRemoteObject(treeId); } private void testGetRemoteObject(ObjectId oid) throws Exception { GeoGIG geogig = geogigData.getGeogig(); final String resource = BASE_URL + "/repo/objects/"; final String url = resource + oid.toString(); MockHttpServletResponse servletResponse; InputStream responseStream; servletResponse = getAsServletResponse(url); assertEquals(200, servletResponse.getStatus()); String contentType = MediaType.APPLICATION_OCTET_STREAM.toString(); assertEquals(contentType, servletResponse.getContentType()); responseStream = getBinaryInputStream(servletResponse); ObjectSerializingFactory factory = DataStreamSerializationFactoryV1.INSTANCE; RevObject actual = factory.read(oid, responseStream); RevObject expected = geogig.command(RevObjectParse.class).setObjectId(oid).call().get(); assertEquals(expected, actual); } /** * Test for resource {@code /rest/<repository>/repo/batchobjects} */ @Test public void testGetBatchedObjects() throws Exception { GeoGIG geogig = geogigData.getGeogig(); Ref head = geogig.command(RefParse.class).setName(Ref.HEAD).call().get(); ObjectId commitId = head.getObjectId(); testGetBatchedRemoteObjects(commitId); } private void testGetBatchedRemoteObjects(ObjectId oid) throws Exception { GeoGIG geogig = geogigData.getGeogig(); final String resource = BASE_URL + "/repo/batchobjects"; final String url = resource; RevObject expected = geogig.command(RevObjectParse.class).setObjectId(oid).call().get(); JsonObject requestBody = new JsonObject(); JsonArray wantList = new JsonArray(); wantList.add(new JsonPrimitive(oid.toString())); requestBody.add("want", wantList); MockHttpServletResponse servletResponse; InputStream responseStream; servletResponse = postAsServletResponse(url, requestBody.toString(), "application/json"); assertEquals(200, servletResponse.getStatus()); String contentType = MediaType.APPLICATION_OCTET_STREAM.toString(); assertEquals(contentType, servletResponse.getContentType()); responseStream = getBinaryInputStream(servletResponse); ObjectSerializingFactory factory = DataStreamSerializationFactoryV1.INSTANCE; List<RevObject> objects = Lists .newArrayList(new ObjectStreamIterator(responseStream, factory)); assertFalse(objects.isEmpty()); RevObject actual = objects.get(objects.size() - 1); assertEquals(expected, actual); } private MockHttpServletResponse assertResponse(String url, String expectedContent) throws Exception { MockHttpServletResponse sr = getAsServletResponse(url); assertEquals(sr.getContentAsString(), 200, sr.getStatus()); String responseBody = sr.getContentAsString(); assertNotNull(responseBody); assertEquals(expectedContent, responseBody); return sr; } private class ObjectStreamIterator extends AbstractIterator<RevObject> { private final InputStream bytes; private final ObjectSerializingFactory formats; public ObjectStreamIterator(InputStream input, ObjectSerializingFactory formats) { this.bytes = input; this.formats = formats; } @Override protected RevObject computeNext() { try { byte[] id = new byte[20]; int len = bytes.read(id, 0, 20); if (len < 0) return endOfData(); if (len != 20) throw new IllegalStateException("We need a 'readFully' operation!"); return formats.read(new ObjectId(id), bytes); } catch (EOFException e) { return endOfData(); } catch (IOException e) { throw Throwables.propagate(e); } } } @Test public void testRemoteAdd() throws Exception { String remoteURL = "http://example.com/geogig/upstream"; final String url = BASE_URL + "/remote?remoteName=upstream&remoteURL=" + remoteURL; MockHttpServletResponse sr = getAsServletResponse(url); assertEquals(200, sr.getStatus()); Document dom = super.dom(new ByteArrayInputStream(sr.getContentAsString().getBytes()), true); // <response><success>true</success><name>upstream</name></response> assertXpathEvaluatesTo("true", "/response/success", dom); assertXpathEvaluatesTo("upstream", "/response/name", dom); dom = getAsDOM(url); print(dom); // <response><success>false</success><error>REMOTE_ALREADY_EXISTS</error></response> assertXpathEvaluatesTo("false", "/response/success", dom); assertXpathEvaluatesTo("REMOTE_ALREADY_EXISTS", "/response/error", dom); } @Test public void testRemoteRemove() throws Exception { String remoteURL = "http://example.com/geogig/upstream"; final String addUrl = BASE_URL + "/remote?remoteURL=" + remoteURL + "&remoteName="; final String removeUrl = BASE_URL + "/remote?remove=true&remoteName="; final String listUrl = BASE_URL + "/remote?list=true"; MockHttpServletResponse sr; Document dom; dom = getAsDOM(addUrl + "upstream"); assertXpathEvaluatesTo("true", "/response/success", dom); assertXpathEvaluatesTo("upstream", "/response/name", dom); dom = getAsDOM(addUrl + "origin"); assertXpathEvaluatesTo("true", "/response/success", dom); assertXpathEvaluatesTo("origin", "/response/name", dom); dom = getAsDOM(listUrl); assertXpathExists("/response/Remote/name[text() = 'upstream']", dom); assertXpathExists("/response/Remote/name[text() = 'origin']", dom); dom = getAsDOM(removeUrl + "upstream"); assertXpathEvaluatesTo("true", "/response/success", dom); assertXpathEvaluatesTo("upstream", "/response/name", dom); dom = getAsDOM(listUrl); assertXpathNotExists("/response/Remote/name[text() = 'upstream']", dom); assertXpathExists("/response/Remote/name[text() = 'origin']", dom); dom = getAsDOM(removeUrl + "origin"); assertXpathEvaluatesTo("true", "/response/success", dom); assertXpathEvaluatesTo("origin", "/response/name", dom); dom = getAsDOM(listUrl); assertXpathNotExists("/response/Remote/name[text() = 'upstream']", dom); assertXpathNotExists("/response/Remote/name[text() = 'origin']", dom); } @Test public void testRemoteUpdate() throws Exception { String remoteURL = "http://example.com/geogig/upstream"; String newURL = "http://new.example.com/geogig/upstream"; final String addUrl = BASE_URL + "/remote?remoteName=upstream&remoteURL=" + remoteURL; final String renameUrl = BASE_URL + "/remote?update=true&remoteName=upstream&newName=new_name&remoteURL=" + newURL; Document dom = getAsDOM(addUrl); assertXpathEvaluatesTo("true", "/response/success", dom); assertXpathEvaluatesTo("upstream", "/response/name", dom); dom = getAsDOM(renameUrl); assertXpathEvaluatesTo("true", "/response/success", dom); assertXpathEvaluatesTo("new_name", "/response/name", dom); } }