diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DataAvailability.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DataAvailability.java index 03d1f58..c7b9c34 100644 --- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DataAvailability.java +++ b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DataAvailability.java @@ -1,6 +1,12 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.mapwithai.backend; +import javax.json.Json; +import javax.json.JsonException; +import javax.json.JsonObject; +import javax.json.JsonValue; +import javax.json.stream.JsonParser; + import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -13,12 +19,6 @@ import java.util.TreeMap; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.json.Json; -import javax.json.JsonException; -import javax.json.JsonObject; -import javax.json.JsonValue; -import javax.json.stream.JsonParser; - import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.io.CachedFile; @@ -157,8 +157,7 @@ public class DataAvailability { * @return the unique instance */ public static DataAvailability getInstance() { - if (instance == null || COUNTRIES.isEmpty() - || MapWithAIPreferenceHelper.getMapWithAIUrl().isEmpty()) { + if (instance == null || COUNTRIES.isEmpty() || MapWithAIPreferenceHelper.getMapWithAIUrl().isEmpty()) { instance = new DataAvailability(); } return instance; diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DataConflationSender.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DataConflationSender.java index 3907d11..cb93e21 100644 --- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DataConflationSender.java +++ b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DataConflationSender.java @@ -31,6 +31,7 @@ import org.openstreetmap.josm.io.OsmWriterFactory; import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAICategory; import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIConflationCategory; import org.openstreetmap.josm.tools.Logging; +import org.openstreetmap.josm.tools.Utils; /** * Conflate data with a third party server @@ -66,39 +67,41 @@ public class DataConflationSender implements RunnableFuture { @Override public void run() { String url = MapWithAIConflationCategory.conflationUrlFor(category); - this.client = HttpClients.createDefault(); - try (CloseableHttpClient currentClient = this.client) { - MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create(); - if (osm != null) { + if (!Utils.isBlank(url)) { + this.client = HttpClients.createDefault(); + try (CloseableHttpClient currentClient = this.client) { + MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create(); + if (osm != null) { + StringWriter output = new StringWriter(); + try (OsmWriter writer = OsmWriterFactory.createOsmWriter(new PrintWriter(output), true, "0.6")) { + writer.write(osm); + multipartEntityBuilder.addTextBody("openstreetmap", output.toString(), + ContentType.APPLICATION_XML); + } + } + // We need to reset the writers to avoid writing previous streams StringWriter output = new StringWriter(); try (OsmWriter writer = OsmWriterFactory.createOsmWriter(new PrintWriter(output), true, "0.6")) { - writer.write(osm); - multipartEntityBuilder.addTextBody("openstreetmap", output.toString(), ContentType.APPLICATION_XML); + writer.write(external); + multipartEntityBuilder.addTextBody("external", output.toString(), ContentType.APPLICATION_XML); } - } - // We need to reset the writers to avoid writing previous streams - StringWriter output = new StringWriter(); - try (OsmWriter writer = OsmWriterFactory.createOsmWriter(new PrintWriter(output), true, "0.6")) { - writer.write(external); - multipartEntityBuilder.addTextBody("external", output.toString(), ContentType.APPLICATION_XML); - } - HttpEntity postData = multipartEntityBuilder.build(); - HttpUriRequest request = RequestBuilder.post(url).setEntity(postData).build(); + HttpEntity postData = multipartEntityBuilder.build(); + HttpUriRequest request = RequestBuilder.post(url).setEntity(postData).build(); - HttpResponse response = currentClient.execute(request); - if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { - conflatedData = OsmReader.parseDataSet(response.getEntity().getContent(), NullProgressMonitor.INSTANCE, - OsmReader.Options.SAVE_ORIGINAL_ID); - } else { - conflatedData = null; + HttpResponse response = currentClient.execute(request); + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + conflatedData = OsmReader.parseDataSet(response.getEntity().getContent(), + NullProgressMonitor.INSTANCE, OsmReader.Options.SAVE_ORIGINAL_ID); + } else { + conflatedData = null; + } + ProtocolVersion protocolVersion = response.getStatusLine().getProtocolVersion(); + Logging.info(request.getMethod() + ' ' + url + " -> " + protocolVersion.getProtocol() + '/' + + protocolVersion.getMajor() + '.' + protocolVersion.getMinor() + ' ' + + response.getStatusLine().getStatusCode()); + } catch (IOException | UnsupportedOperationException | IllegalDataException e) { + Logging.error(e); } - ProtocolVersion protocolVersion = response.getStatusLine().getProtocolVersion(); - Logging.info(new StringBuilder(request.getMethod()).append(' ').append(url).append(" -> ") - .append(protocolVersion.getProtocol()).append('/').append(protocolVersion.getMajor()).append('.') - .append(protocolVersion.getMinor()).append(' ').append(response.getStatusLine().getStatusCode()) - .toString()); - } catch (IOException | UnsupportedOperationException | IllegalDataException e) { - Logging.error(e); } this.done = true; synchronized (this) { diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/data/mapwithai/MapWithAILayerInfo.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/data/mapwithai/MapWithAILayerInfo.java index 4c255b5..435b42f 100644 --- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/data/mapwithai/MapWithAILayerInfo.java +++ b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/data/mapwithai/MapWithAILayerInfo.java @@ -3,6 +3,9 @@ package org.openstreetmap.josm.plugins.mapwithai.data.mapwithai; import static org.openstreetmap.josm.tools.I18n.tr; +import javax.annotation.Nonnull; +import javax.swing.SwingUtilities; + import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -20,9 +23,6 @@ import java.util.concurrent.ForkJoinPool; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; -import javax.annotation.Nonnull; -import javax.swing.SwingUtilities; - import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo; import org.openstreetmap.josm.actions.ExpertToggleAction; import org.openstreetmap.josm.data.Preferences; diff --git a/test/unit/org/openstreetmap/josm/plugins/mapwithai/backend/DataConflationSenderTest.java b/test/unit/org/openstreetmap/josm/plugins/mapwithai/backend/DataConflationSenderTest.java new file mode 100644 index 0000000..9dfb250 --- /dev/null +++ b/test/unit/org/openstreetmap/josm/plugins/mapwithai/backend/DataConflationSenderTest.java @@ -0,0 +1,157 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.plugins.mapwithai.backend; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.function.ThrowingSupplier; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.openstreetmap.josm.data.coor.LatLon; +import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.data.osm.Node; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAICategory; +import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIConflationCategory; +import org.openstreetmap.josm.plugins.mapwithai.testutils.annotations.MapWithAISources; +import org.openstreetmap.josm.plugins.mapwithai.testutils.annotations.Wiremock; +import org.openstreetmap.josm.testutils.JOSMTestRules; +import org.openstreetmap.josm.testutils.annotations.BasicPreferences; +import org.openstreetmap.josm.testutils.annotations.BasicWiremock; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; + +import mockit.Invocation; +import mockit.Mock; +import mockit.MockUp; + +/** + * Test class for {@link DataConflationSender} + * + * @author Taylor Smock + */ +@BasicPreferences +@Wiremock +@MapWithAISources +class DataConflationSenderTest { + // Needed for HTTP factory + @RegisterExtension + JOSMTestRules josmTestRules = new JOSMTestRules(); + + @BasicWiremock + WireMockServer wireMockServer; + + static class MapWithAIConflationCategoryMock extends MockUp { + static String url; + + @Mock + public static String conflationUrlFor(final Invocation invocation, final MapWithAICategory category) { + if (url == null) { + return invocation.proceed(category); + } else { + return url; + } + } + } + + @BeforeEach + void beforeEach() { + MapWithAIConflationCategoryMock.url = null; + } + + @Test + void testEmptyUrl() { + MapWithAIConflationCategoryMock.url = ""; + new MapWithAIConflationCategoryMock(); + + final DataSet external = new DataSet(new Node(LatLon.NORTH_POLE)); + final DataSet openstreetmap = new DataSet(new Node(LatLon.ZERO)); + final DataConflationSender dataConflationSender = new DataConflationSender(MapWithAICategory.OTHER, + openstreetmap, external); + dataConflationSender.run(); + assertNull(assertDoesNotThrow((ThrowingSupplier) dataConflationSender::get)); + } + + @Test + void testWorkingUrl() { + MapWithAIConflationCategoryMock.url = wireMockServer.baseUrl() + "/conflate"; + final StubMapping stubMapping = wireMockServer + .stubFor(WireMock.post("/conflate").willReturn(WireMock.aResponse().withBody( + ""))); + new MapWithAIConflationCategoryMock(); + + final DataSet external = new DataSet(new Node(LatLon.NORTH_POLE)); + final DataSet openstreetmap = new DataSet(new Node(LatLon.ZERO)); + final DataConflationSender dataConflationSender = new DataConflationSender(MapWithAICategory.OTHER, + openstreetmap, external); + dataConflationSender.run(); + final DataSet conflated = assertDoesNotThrow((ThrowingSupplier) dataConflationSender::get); + assertNotNull(conflated); + assertEquals(1, conflated.allPrimitives().size()); + assertEquals(1, conflated.getNodes().size()); + final Node conflatedNode = conflated.getNodes().iterator().next(); + assertEquals(new LatLon(89, 0.1), conflatedNode.getCoor()); + assertEquals(1, wireMockServer.getAllServeEvents().stream() + .filter(serveEvent -> stubMapping.equals(serveEvent.getStubMapping())).count()); + } + + @Test + void testWorkingUrlTimeout() { + MapWithAIConflationCategoryMock.url = wireMockServer.baseUrl() + "/conflate"; + final StubMapping stubMapping = wireMockServer.stubFor(WireMock.post("/conflate") + .willReturn(WireMock.aResponse().withBody( + "") + .withFixedDelay(500))); + new MapWithAIConflationCategoryMock(); + + final DataSet external = new DataSet(new Node(LatLon.NORTH_POLE)); + final DataSet openstreetmap = new DataSet(new Node(LatLon.ZERO)); + final DataConflationSender dataConflationSender = new DataConflationSender(MapWithAICategory.OTHER, + openstreetmap, external); + MainApplication.worker.execute(dataConflationSender); + final DataSet conflated = assertDoesNotThrow(() -> dataConflationSender.get(1, TimeUnit.MILLISECONDS)); + assertNotNull(conflated); + assertEquals(1, conflated.allPrimitives().size()); + assertEquals(1, conflated.getNodes().size()); + final Node conflatedNode = conflated.getNodes().iterator().next(); + assertEquals(new LatLon(89, 0.1), conflatedNode.getCoor()); + assertEquals(1, wireMockServer.getAllServeEvents().stream() + .filter(serveEvent -> stubMapping.equals(serveEvent.getStubMapping())).count()); + } + + static Stream testNonWorkingUrl() { + return Stream.of(Arguments.of(WireMock.noContent()), Arguments.of(WireMock.notFound()), + Arguments.of(WireMock.forbidden()), Arguments.of(WireMock.serverError()), + Arguments.of(WireMock.unauthorized())); + } + + @ParameterizedTest + @MethodSource + void testNonWorkingUrl(final ResponseDefinitionBuilder response) { + MapWithAIConflationCategoryMock.url = wireMockServer.baseUrl() + "/conflate"; + final StubMapping stubMapping = wireMockServer.stubFor(WireMock.post("/conflate").willReturn(response)); + new MapWithAIConflationCategoryMock(); + + final DataSet external = new DataSet(new Node(LatLon.NORTH_POLE)); + final DataSet openstreetmap = new DataSet(new Node(LatLon.ZERO)); + final DataConflationSender dataConflationSender = new DataConflationSender(MapWithAICategory.OTHER, + openstreetmap, external); + dataConflationSender.run(); + final DataSet conflated = assertDoesNotThrow((ThrowingSupplier) dataConflationSender::get); + assertNull(conflated); + assertEquals(1, wireMockServer.getAllServeEvents().stream() + .filter(serveEvent -> stubMapping.equals(serveEvent.getStubMapping())).count()); + } +} diff --git a/test/unit/org/openstreetmap/josm/plugins/mapwithai/gui/download/MapWithAIDownloadSourceTypeTest.java b/test/unit/org/openstreetmap/josm/plugins/mapwithai/gui/download/MapWithAIDownloadSourceTypeTest.java index 9ce1c97..3a3bc69 100644 --- a/test/unit/org/openstreetmap/josm/plugins/mapwithai/gui/download/MapWithAIDownloadSourceTypeTest.java +++ b/test/unit/org/openstreetmap/josm/plugins/mapwithai/gui/download/MapWithAIDownloadSourceTypeTest.java @@ -6,10 +6,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.concurrent.atomic.AtomicBoolean; - import javax.swing.JCheckBox; +import java.util.concurrent.atomic.AtomicBoolean; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.openstreetmap.josm.data.Bounds; diff --git a/test/unit/org/openstreetmap/josm/plugins/mapwithai/testutils/annotations/Wiremock.java b/test/unit/org/openstreetmap/josm/plugins/mapwithai/testutils/annotations/Wiremock.java index d23d9af..ee8bca7 100644 --- a/test/unit/org/openstreetmap/josm/plugins/mapwithai/testutils/annotations/Wiremock.java +++ b/test/unit/org/openstreetmap/josm/plugins/mapwithai/testutils/annotations/Wiremock.java @@ -26,6 +26,7 @@ import org.openstreetmap.josm.testutils.annotations.BasicWiremock; import org.openstreetmap.josm.testutils.annotations.HTTP; import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.FileSource; import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.extension.ResponseTransformer; @@ -71,6 +72,7 @@ public @interface Wiremock { @Override public Response transform(Request request, Response response, FileSource files, Parameters parameters) { if (!request.getUrl().endsWith("/capabilities") + && response.getHeaders().getContentTypeHeader().mimeTypePart() != null && !response.getHeaders().getContentTypeHeader().mimeTypePart().contains("application/zip")) { String origBody = response.getBodyAsString(); String newBody = origBody.replaceAll("https?://.*?/", @@ -152,6 +154,7 @@ public @interface Wiremock { public String getMapWithAIPaintStyle() { return replaceUrl(getWiremock(this.context), MapWithAIUrls.getInstance().getMapWithAIPaintStyle()); } + @Override public void beforeAll(ExtensionContext context) throws Exception { super.beforeAll(context); @@ -171,10 +174,20 @@ public @interface Wiremock { @Override public void beforeEach(ExtensionContext context) throws Exception { final Optional annotation = AnnotationUtils.findFirstParentAnnotation(context, Wiremock.class); + this.context = context; if (annotation.isPresent()) { this.beforeAll(context); } + final WireMockServer wireMockServer = getWiremock(context); + super.beforeEach(context); + + if (wireMockServer.getStubMappings().stream().filter(mapping -> mapping.getRequest().getUrl() != null) + .noneMatch(mapping -> mapping.getRequest().getUrl() + .equals("/JOSM_MapWithAI/json/conflation_servers.json"))) { + wireMockServer.stubFor(WireMock.get("/JOSM_MapWithAI/json/conflation_servers.json") + .willReturn(WireMock.aResponse().withBody("{}")).atPriority(-5)); + } } @Override @@ -189,6 +202,10 @@ public @interface Wiremock { MapWithAIConfig.setUrlsProvider(new InvalidMapWithAIUrls()); } } + + public WireMockServer getWireMockServer() { + return getWiremock(context); + } } class InvalidMapWithAIUrls implements IMapWithAIUrls {