- Timestamp:
- 2024-07-25T20:56:22+02:00 (4 months ago)
- Location:
- trunk
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/LoadAndZoomHandler.java
r18871 r19152 6 6 import java.awt.geom.Area; 7 7 import java.awt.geom.Rectangle2D; 8 import java.util.Arrays;9 8 import java.util.Collection; 10 9 import java.util.Collections; … … 18 17 import java.util.concurrent.TimeoutException; 19 18 import java.util.stream.Collectors; 19 import java.util.stream.Stream; 20 20 21 21 import javax.swing.JOptionPane; … … 65 65 public static final String command2 = "zoom"; 66 66 private static final String CURRENT_SELECTION = "currentselection"; 67 private static final String SELECT = "select"; 68 private static final String ADDTAGS = "addtags"; 69 private static final String CHANGESET_COMMENT = "changeset_comment"; 70 private static final String CHANGESET_SOURCE = "changeset_source"; 71 private static final String CHANGESET_HASHTAGS = "changeset_hashtags"; 72 private static final String CHANGESET_TAGS = "changeset_tags"; 73 private static final String SEARCH = "search"; 67 74 68 75 // Mandatory arguments … … 81 88 String msg = tr("Remote Control has been asked to load data from the API.") + 82 89 "<br>" + tr("Bounding box: ") + new BBox(minlon, minlat, maxlon, maxlat).toStringCSV(", "); 83 if (args.containsKey( "select") && !toSelect.isEmpty()) {90 if (args.containsKey(SELECT) && !toSelect.isEmpty()) { 84 91 msg += "<br>" + tr("Selection: {0}", toSelect.size()); 85 92 } … … 94 101 @Override 95 102 public String[] getOptionalParams() { 96 return new String[] {"new_layer", "layer_name", "addtags", "select", "zoom_mode",97 "changeset_comment", "changeset_source", "changeset_hashtags", "changeset_tags",98 "search", "layer_locked", "download_policy", "upload_policy"};103 return new String[] {"new_layer", "layer_name", ADDTAGS, SELECT, "zoom_mode", 104 CHANGESET_COMMENT, CHANGESET_SOURCE, CHANGESET_HASHTAGS, CHANGESET_TAGS, 105 SEARCH, "layer_locked", "download_policy", "upload_policy"}; 99 106 } 100 107 … … 127 134 @Override 128 135 protected void handleRequest() throws RequestHandlerErrorException { 136 download(); 137 /* 138 * deselect objects if parameter addtags given 139 */ 140 if (args.containsKey(ADDTAGS) && !isKeepingCurrentSelection) { 141 GuiHelper.executeByMainWorkerInEDT(() -> { 142 DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 143 if (ds == null) // e.g. download failed 144 return; 145 ds.clearSelection(); 146 }); 147 } 148 149 Collection<OsmPrimitive> forTagAdd = performSearchZoom(); 150 151 // This comes before the other changeset tags, so that they can be overridden 152 parseChangesetTags(args); 153 154 // add changeset tags after download if necessary 155 addChangesetTags(); 156 157 // add tags to objects 158 addTags(forTagAdd); 159 } 160 161 private void download() throws RequestHandlerErrorException { 129 162 DownloadOsmTask osmTask = new DownloadOsmTask(); 130 163 try { … … 137 170 Area toDownload = null; 138 171 if (!settings.isNewLayer()) { 139 // find out whether some data has already been downloaded 140 Area present = null; 141 DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 142 if (ds != null) { 143 present = ds.getDataSourceArea(); 144 } 145 if (present != null && !present.isEmpty()) { 146 toDownload = new Area(new Rectangle2D.Double(minlon, minlat, maxlon-minlon, maxlat-minlat)); 147 toDownload.subtract(present); 148 if (!toDownload.isEmpty()) { 149 // the result might not be a rectangle (L shaped etc) 150 Rectangle2D downloadBounds = toDownload.getBounds2D(); 151 minlat = downloadBounds.getMinY(); 152 minlon = downloadBounds.getMinX(); 153 maxlat = downloadBounds.getMaxY(); 154 maxlon = downloadBounds.getMaxX(); 155 } 156 } 172 toDownload = removeAlreadyDownloadedArea(); 157 173 } 158 174 if (toDownload != null && toDownload.isEmpty()) { 159 175 Logging.info("RemoteControl: no download necessary"); 160 176 } else { 161 Future<?> future = MainApplication.worker.submit( 162 new PostDownloadHandler(osmTask, osmTask.download(settings, new Bounds(minlat, minlon, maxlat, maxlon), 163 null /* let the task manage the progress monitor */))); 164 GuiHelper.executeByMainWorkerInEDT(() -> { 165 try { 166 future.get(OSM_DOWNLOAD_TIMEOUT.get(), TimeUnit.SECONDS); 167 if (osmTask.isFailed()) { 168 Object error = osmTask.getErrorObjects().get(0); 169 if (error instanceof OsmApiException) { 170 throw (OsmApiException) error; 171 } 172 List<Throwable> exceptions = osmTask.getErrorObjects().stream() 173 .filter(Throwable.class::isInstance).map(Throwable.class::cast) 174 .collect(Collectors.toList()); 175 OsmTransferException osmTransferException = 176 new OsmTransferException(String.join(", ", osmTask.getErrorMessages())); 177 if (!exceptions.isEmpty()) { 178 osmTransferException.initCause(exceptions.get(0)); 179 exceptions.remove(0); 180 exceptions.forEach(osmTransferException::addSuppressed); 181 } 182 throw osmTransferException; 183 } 184 } catch (InterruptedException | ExecutionException | TimeoutException | 185 OsmTransferException | RuntimeException ex) { // NOPMD 186 ExceptionDialogUtil.explainException(ex); 187 } 188 }); 177 performDownload(osmTask, settings); 189 178 } 190 179 } … … 195 184 throw new RequestHandlerErrorException(ex); 196 185 } 197 198 /* 199 * deselect objects if parameter addtags given 200 */ 201 if (args.containsKey("addtags") && !isKeepingCurrentSelection) { 202 GuiHelper.executeByMainWorkerInEDT(() -> { 203 DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 204 if (ds == null) // e.g. download failed 205 return; 206 ds.clearSelection(); 207 }); 208 } 209 186 } 187 188 /** 189 * Remove areas that has already been downloaded 190 * @return The area to download 191 */ 192 private Area removeAlreadyDownloadedArea() { 193 // find out whether some data has already been downloaded 194 Area toDownload = null; 195 Area present = null; 196 DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 197 if (ds != null) { 198 present = ds.getDataSourceArea(); 199 } 200 if (present != null && !present.isEmpty()) { 201 toDownload = new Area(new Rectangle2D.Double(minlon, minlat, maxlon-minlon, maxlat-minlat)); 202 toDownload.subtract(present); 203 if (!toDownload.isEmpty()) { 204 // the result might not be a rectangle (L shaped etc) 205 Rectangle2D downloadBounds = toDownload.getBounds2D(); 206 minlat = downloadBounds.getMinY(); 207 minlon = downloadBounds.getMinX(); 208 maxlat = downloadBounds.getMaxY(); 209 maxlon = downloadBounds.getMaxX(); 210 } 211 } 212 return toDownload; 213 } 214 215 private void performDownload(DownloadOsmTask osmTask, DownloadParams settings) { 216 Future<?> future = MainApplication.worker.submit( 217 new PostDownloadHandler(osmTask, osmTask.download(settings, new Bounds(minlat, minlon, maxlat, maxlon), 218 null /* let the task manage the progress monitor */))); 219 GuiHelper.executeByMainWorkerInEDT(() -> { 220 try { 221 future.get(OSM_DOWNLOAD_TIMEOUT.get(), TimeUnit.SECONDS); 222 if (osmTask.isFailed()) { 223 Object error = osmTask.getErrorObjects().get(0); 224 if (error instanceof OsmApiException) { 225 throw (OsmApiException) error; 226 } 227 List<Throwable> exceptions = osmTask.getErrorObjects().stream() 228 .filter(Throwable.class::isInstance).map(Throwable.class::cast) 229 .collect(Collectors.toList()); 230 OsmTransferException osmTransferException = 231 new OsmTransferException(String.join(", ", osmTask.getErrorMessages())); 232 if (!exceptions.isEmpty()) { 233 osmTransferException.initCause(exceptions.get(0)); 234 exceptions.remove(0); 235 exceptions.forEach(osmTransferException::addSuppressed); 236 } 237 throw osmTransferException; 238 } 239 } catch (InterruptedException ex) { 240 Thread.currentThread().interrupt(); 241 ExceptionDialogUtil.explainException(ex); 242 } catch (ExecutionException | TimeoutException | 243 OsmTransferException | RuntimeException ex) { // NOPMD 244 ExceptionDialogUtil.explainException(ex); 245 } 246 }); 247 } 248 249 private Collection<OsmPrimitive> performSearchZoom() throws RequestHandlerErrorException { 210 250 final Collection<OsmPrimitive> forTagAdd = new LinkedHashSet<>(); 211 251 final Bounds bbox = new Bounds(minlat, minlon, maxlat, maxlon); 212 if (args.containsKey( "select") && PermissionPrefWithDefault.CHANGE_SELECTION.isAllowed()) {252 if (args.containsKey(SELECT) && PermissionPrefWithDefault.CHANGE_SELECTION.isAllowed()) { 213 253 // select objects after downloading, zoom to selection. 214 GuiHelper.executeByMainWorkerInEDT(() -> { 215 Set<OsmPrimitive> newSel = new LinkedHashSet<>(); 216 DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 217 if (ds == null) // e.g. download failed 218 return; 219 for (SimplePrimitiveId id : toSelect) { 220 final OsmPrimitive p = ds.getPrimitiveById(id); 221 if (p != null) { 222 newSel.add(p); 223 forTagAdd.add(p); 224 } 225 } 226 if (isKeepingCurrentSelection) { 227 Collection<OsmPrimitive> sel = ds.getSelected(); 228 newSel.addAll(sel); 229 forTagAdd.addAll(sel); 230 } 231 toSelect.clear(); 232 ds.setSelected(newSel); 233 zoom(newSel, bbox); 234 MapFrame map = MainApplication.getMap(); 235 if (MainApplication.isDisplayingMapView() && map.relationListDialog != null) { 236 map.relationListDialog.selectRelations(null); // unselect all relations to fix #7342 237 map.relationListDialog.dataChanged(null); 238 map.relationListDialog.selectRelations(Utils.filteredCollection(newSel, Relation.class)); 239 } 240 }); 241 } else if (args.containsKey("search") && PermissionPrefWithDefault.CHANGE_SELECTION.isAllowed()) { 242 try { 243 final SearchCompiler.Match search = SearchCompiler.compile(args.get("search")); 244 MainApplication.worker.submit(() -> { 245 final DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 246 final Collection<OsmPrimitive> filteredPrimitives = SubclassFilteredCollection.filter(ds.allPrimitives(), search); 247 ds.setSelected(filteredPrimitives); 248 forTagAdd.addAll(filteredPrimitives); 249 zoom(filteredPrimitives, bbox); 250 }); 251 } catch (SearchParseError ex) { 252 Logging.error(ex); 253 throw new RequestHandlerErrorException(ex); 254 } 254 GuiHelper.executeByMainWorkerInEDT(() -> selectAndZoom(forTagAdd, bbox)); 255 } else if (args.containsKey(SEARCH) && PermissionPrefWithDefault.CHANGE_SELECTION.isAllowed()) { 256 searchAndZoom(forTagAdd, bbox); 255 257 } else { 256 258 // after downloading, zoom to downloaded area. 257 zoom(Collections.<OsmPrimitive>emptySet(), bbox); 258 } 259 260 // This comes before the other changeset tags, so that they can be overridden 261 parseChangesetTags(args); 262 263 // add changeset tags after download if necessary 264 if (args.containsKey("changeset_comment") || args.containsKey("changeset_source") || args.containsKey("changeset_hashtags")) { 259 zoom(Collections.emptySet(), bbox); 260 } 261 return forTagAdd; 262 } 263 264 private void selectAndZoom(Collection<OsmPrimitive> forTagAdd, Bounds bbox) { 265 Set<OsmPrimitive> newSel = new LinkedHashSet<>(); 266 DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 267 if (ds == null) // e.g. download failed 268 return; 269 for (SimplePrimitiveId id : toSelect) { 270 final OsmPrimitive p = ds.getPrimitiveById(id); 271 if (p != null) { 272 newSel.add(p); 273 forTagAdd.add(p); 274 } 275 } 276 if (isKeepingCurrentSelection) { 277 Collection<OsmPrimitive> sel = ds.getSelected(); 278 newSel.addAll(sel); 279 forTagAdd.addAll(sel); 280 } 281 toSelect.clear(); 282 ds.setSelected(newSel); 283 zoom(newSel, bbox); 284 MapFrame map = MainApplication.getMap(); 285 if (MainApplication.isDisplayingMapView() && map.relationListDialog != null) { 286 map.relationListDialog.selectRelations(null); // unselect all relations to fix #7342 287 map.relationListDialog.dataChanged(null); 288 map.relationListDialog.selectRelations(Utils.filteredCollection(newSel, Relation.class)); 289 } 290 } 291 292 private void searchAndZoom(Collection<OsmPrimitive> forTagAdd, Bounds bbox) throws RequestHandlerErrorException { 293 try { 294 final SearchCompiler.Match search = SearchCompiler.compile(args.get(SEARCH)); 265 295 MainApplication.worker.submit(() -> { 266 DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 267 if (ds != null) { 268 for (String tag : Arrays.asList("changeset_comment", "changeset_source", "changeset_hashtags")) { 269 if (args.containsKey(tag)) { 270 final String tagKey = tag.substring("changeset_".length()); 271 final String value = args.get(tag); 272 if (!Utils.isStripEmpty(value)) { 273 ds.addChangeSetTag(tagKey, value); 274 } else { 275 ds.addChangeSetTag(tagKey, null); 276 } 277 } 278 } 279 } 296 final DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 297 final Collection<OsmPrimitive> filteredPrimitives = SubclassFilteredCollection.filter(ds.allPrimitives(), search); 298 ds.setSelected(filteredPrimitives); 299 forTagAdd.addAll(filteredPrimitives); 300 zoom(filteredPrimitives, bbox); 280 301 }); 281 } 282 283 // add tags to objects 284 if (args.containsKey("addtags")) { 302 } catch (SearchParseError ex) { 303 Logging.error(ex); 304 throw new RequestHandlerErrorException(ex); 305 } 306 } 307 308 private void addChangesetTags() { 309 List<String> values = Stream.of(CHANGESET_COMMENT, CHANGESET_SOURCE, CHANGESET_HASHTAGS) 310 .filter(args::containsKey).collect(Collectors.toList()); 311 if (values.isEmpty()) { 312 return; 313 } 314 MainApplication.worker.submit(() -> { 315 DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 316 if (ds != null) { 317 for (String tag : values) { 318 final String tagKey = tag.substring("changeset_".length()); 319 final String value = args.get(tag); 320 if (!Utils.isStripEmpty(value)) { 321 ds.addChangeSetTag(tagKey, value); 322 } else { 323 ds.addChangeSetTag(tagKey, null); 324 } 325 } 326 } 327 }); 328 } 329 330 private void addTags(Collection<OsmPrimitive> forTagAdd) { 331 if (args.containsKey(ADDTAGS)) { 285 332 // needs to run in EDT since forTagAdd is updated in EDT as well 286 333 GuiHelper.executeByMainWorkerInEDT(() -> { … … 290 337 new Notification(isKeepingCurrentSelection 291 338 ? tr("You clicked on a JOSM remotecontrol link that would apply tags onto selected objects.\n" 292 293 339 + "Since no objects have been selected before this click, no tags were added.\n" 340 + "Select one or more objects and click the link again.") 294 341 : tr("You clicked on a JOSM remotecontrol link that would apply tags onto objects.\n" 295 296 297 298 342 + "Unfortunately that link seems to be broken.\n" 343 + "Technical explanation: the URL query parameter ''select='' or ''search='' has an invalid value.\n" 344 + "Ask someone at the origin of the clicked link to fix this.") 345 ).setIcon(JOptionPane.WARNING_MESSAGE).setDuration(Notification.TIME_LONG).show(); 299 346 } 300 347 }); … … 303 350 304 351 static void parseChangesetTags(Map<String, String> args) { 305 if (args.containsKey( "changeset_tags")) {352 if (args.containsKey(CHANGESET_TAGS)) { 306 353 MainApplication.worker.submit(() -> { 307 354 DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 308 355 if (ds != null) { 309 AddTagsDialog.parseUrlTagsToKeyValues(args.get( "changeset_tags")).forEach(ds::addChangeSetTag);356 AddTagsDialog.parseUrlTagsToKeyValues(args.get(CHANGESET_TAGS)).forEach(ds::addChangeSetTag); 310 357 } 311 358 }); … … 366 413 367 414 // Process optional argument 'select' 368 if (args != null && args.containsKey("select")) { 415 validateSelect(); 416 } 417 418 private void validateSelect() { 419 if (args != null && args.containsKey(SELECT)) { 369 420 toSelect.clear(); 370 for (String item : args.get( "select").split(",", -1)) {421 for (String item : args.get(SELECT).split(",", -1)) { 371 422 if (!item.isEmpty()) { 372 423 if (CURRENT_SELECTION.equalsIgnoreCase(item)) { -
trunk/test/unit/org/openstreetmap/josm/io/remotecontrol/handler/LoadAndZoomHandlerTest.java
r18037 r19152 2 2 package org.openstreetmap.josm.io.remotecontrol.handler; 3 3 4 import static org.junit.jupiter.api.Assertions.assertAll; 4 5 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 5 6 import static org.junit.jupiter.api.Assertions.assertEquals; 7 import static org.junit.jupiter.api.Assertions.assertNotNull; 8 import static org.junit.jupiter.api.Assertions.assertNull; 6 9 import static org.junit.jupiter.api.Assertions.assertThrows; 7 10 import static org.junit.jupiter.api.Assertions.assertTrue; 11 12 import java.net.URLEncoder; 13 import java.nio.charset.StandardCharsets; 14 import java.util.Collection; 15 16 import com.github.tomakehurst.wiremock.client.WireMock; 17 import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; 18 import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder; 19 import org.junit.jupiter.api.BeforeEach; 20 import org.junit.jupiter.api.extension.ExtendWith; 21 import org.openstreetmap.josm.data.osm.DataSet; 22 import org.openstreetmap.josm.data.osm.Node; 23 import org.openstreetmap.josm.data.osm.OsmPrimitive; 24 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 25 import org.openstreetmap.josm.gui.MainApplication; 8 26 import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerBadRequestException; 9 27 import org.openstreetmap.josm.testutils.annotations.BasicPreferences; 10 28 11 29 import org.junit.jupiter.api.Test; 30 import org.openstreetmap.josm.testutils.annotations.BasicWiremock; 31 import org.openstreetmap.josm.testutils.annotations.Main; 32 import org.openstreetmap.josm.testutils.annotations.Projection; 33 import org.openstreetmap.josm.testutils.annotations.ThreadSync; 12 34 13 35 /** 14 36 * Unit tests of {@link LoadAndZoomHandler} class. 15 37 */ 38 @BasicPreferences 39 @BasicWiremock 40 @ExtendWith(BasicWiremock.OsmApiExtension.class) 16 41 class LoadAndZoomHandlerTest { 42 private static final String DEFAULT_BBOX_URL = "https://localhost/load_and_zoom?left=0&bottom=0&right=0.001&top=0.001"; 17 43 private static LoadAndZoomHandler newHandler(String url) throws RequestHandlerBadRequestException { 18 44 LoadAndZoomHandler req = new LoadAndZoomHandler(); 45 req.myCommand = LoadAndZoomHandler.command; 19 46 if (url != null) 20 47 req.setUrl(url); … … 22 49 } 23 50 51 private static void syncThreads() { 52 // There are calls to the worker thread and EDT 53 new ThreadSync.ThreadSyncExtension().threadSync(); 54 } 55 56 @BeforeEach 57 void setup(WireMockRuntimeInfo wireMockRuntimeInfo) { 58 String common = "visible=\"true\" version=\"1\" changeset=\"1\" timestamp=\"2000-01-01T00:00:00Z\" user=\"tsmock\" uid=\"1\""; 59 wireMockRuntimeInfo.getWireMock().register(WireMock.get("/api/0.6/map?bbox=0.0,0.0,0.001,0.001") 60 .willReturn(WireMock.aResponse().withBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + 61 "<osm version=\"0.6\" generator=\"hand\" copyright=\"JOSM\" attribution=\"\" license=\"\">\n" + 62 " <bounds minlat=\"0\" minlon=\"0\" maxlat=\"0.001\" maxlon=\"0.001\"/>\n" + 63 " <node id=\"1\" " + common + " lat=\"0\" lon=\"0\"/>\n" + 64 " <node id=\"2\" " + common + " lat=\"0.0001\" lon=\"0.0001\"/>\n" + 65 " <node id=\"3\" " + common + " lat=\"0.0002\" lon=\"0.0002\"/>\n" + 66 "</osm>"))); 67 } 68 24 69 /** 25 70 * Unit test for bad request - no param. 26 * @throws Exception if any error occurs27 */28 @Test29 void testBadRequestNoParam() throws Exception {30 Exception e = assertThrows(RequestHandlerBadRequestException.class, () -> newHandler(null).handle());71 */ 72 @Test 73 void testBadRequestNoParam() { 74 final LoadAndZoomHandler handler = assertDoesNotThrow(() -> newHandler(null)); 75 Exception e = assertThrows(RequestHandlerBadRequestException.class, handler::handle); 31 76 assertEquals("NumberFormatException (empty String)", e.getMessage()); 32 77 } … … 34 79 /** 35 80 * Unit test for bad request - invalid URL. 36 * @throws Exception if any error occurs37 */38 @Test39 void testBadRequestInvalidUrl() throws Exception {40 Exception e = assertThrows(RequestHandlerBadRequestException.class, () -> newHandler("invalid_url").handle());81 */ 82 @Test 83 void testBadRequestInvalidUrl() { 84 final LoadAndZoomHandler handler = assertDoesNotThrow(() -> newHandler("invalid_url")); 85 Exception e = assertThrows(RequestHandlerBadRequestException.class, handler::handle); 41 86 assertEquals("The following keys are mandatory, but have not been provided: bottom, top, left, right", e.getMessage()); 42 87 } … … 44 89 /** 45 90 * Unit test for bad request - incomplete URL. 46 * @throws Exception if any error occurs47 */48 @Test49 void testBadRequestIncompleteUrl() throws Exception {50 Exception e = assertThrows(RequestHandlerBadRequestException.class, () -> newHandler("https://localhost").handle());91 */ 92 @Test 93 void testBadRequestIncompleteUrl() { 94 final LoadAndZoomHandler handler = assertDoesNotThrow(() -> newHandler("https://localhost")); 95 Exception e = assertThrows(RequestHandlerBadRequestException.class, handler::handle); 51 96 assertEquals("The following keys are mandatory, but have not been provided: bottom, top, left, right", e.getMessage()); 52 97 } 53 98 54 99 /** 55 * Unit test for nominal request - local data file. 56 * @throws Exception if any error occurs 57 */ 58 @Test 59 @BasicPreferences 60 void testNominalRequest() throws Exception { 61 assertDoesNotThrow(() -> newHandler("https://localhost?bottom=0&top=0&left=1&right=1").handle()); 100 * Ensure that a download is called and completed 101 * @param wireMockRuntimeInfo The wiremock information 102 * @throws RequestHandlerBadRequestException If there is an issue with the handler 103 */ 104 @Test 105 void testDownload(WireMockRuntimeInfo wireMockRuntimeInfo) throws RequestHandlerBadRequestException { 106 LoadAndZoomHandler handler = newHandler(DEFAULT_BBOX_URL); 107 assertDoesNotThrow(handler::handle); 108 syncThreads(); 109 final DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 110 assertNotNull(ds); 111 assertAll(() -> assertNotNull(ds.getPrimitiveById(1, OsmPrimitiveType.NODE)), 112 () -> assertNotNull(ds.getPrimitiveById(2, OsmPrimitiveType.NODE)), 113 () -> assertNotNull(ds.getPrimitiveById(3, OsmPrimitiveType.NODE)), 114 () -> assertNull(ds.getPrimitiveById(4, OsmPrimitiveType.NODE)), 115 () -> assertTrue(ds.selectionEmpty())); 116 wireMockRuntimeInfo.getWireMock().verifyThat(1, 117 RequestPatternBuilder.newRequestPattern().withUrl("/api/0.6/map?bbox=0.0,0.0,0.001,0.001")); 118 } 119 120 /** 121 * Ensure that an area isn't downloaded twice 122 * @param wireMockRuntimeInfo The wiremock information 123 * @throws RequestHandlerBadRequestException If there is an issue with the handler 124 */ 125 @Test 126 void testDoubleDownload(WireMockRuntimeInfo wireMockRuntimeInfo) throws RequestHandlerBadRequestException { 127 testDownload(wireMockRuntimeInfo); 128 testDownload(wireMockRuntimeInfo); 129 // testDownload checks that the URL has been called once. Since it doesn't reset anything, we don't need 130 // a specific test here. 131 } 132 133 /** 134 * Ensure that an overlapping area is trimmed before download 135 * @param wireMockRuntimeInfo The wiremock information 136 * @throws RequestHandlerBadRequestException If there is an issue with the handler 137 */ 138 @Test 139 void testOverlappingArea(WireMockRuntimeInfo wireMockRuntimeInfo) throws RequestHandlerBadRequestException { 140 LoadAndZoomHandler handler = newHandler(DEFAULT_BBOX_URL); 141 assertDoesNotThrow(handler::handle); 142 syncThreads(); 143 // The scientific notation is ok server-side. 144 final String mapCall = "/api/0.6/map?bbox=2.5E-4,0.001,7.5E-4,0.00125"; 145 final String commonNode = "visible=\"true\" version=\"1\" changeset=\"1\" timestamp=\"2000-01-01T00:00:00Z\" user=\"tsmock\" uid=\"1\""; 146 wireMockRuntimeInfo.getWireMock().register(WireMock.get(mapCall) 147 .willReturn(WireMock.aResponse().withBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + 148 "<osm version=\"0.6\" generator=\"hand\" copyright=\"JOSM\" attribution=\"\" license=\"\">\n" + 149 " <bounds minlat=\"0.001\" minlon=\"0.00025\" maxlat=\"0.00125\" maxlon=\"0.00075\"/>\n" + 150 " <node id=\"4\" " + commonNode + " lat=\"0.00111\" lon=\"0.00026\"/>\n" + 151 " <node id=\"5\" " + commonNode + " lat=\"0.0011\" lon=\"0.00025\"/>\n" + 152 " <node id=\"6\" " + commonNode + " lat=\"0.0012\" lon=\"0.000251\"/>\n" + 153 "</osm>"))); 154 String request = "https://localhost/load_and_zoom?left=0.00025&bottom=0.00025&right=0.00075&top=0.00125"; 155 handler = newHandler(request); 156 assertDoesNotThrow(handler::handle); 157 syncThreads(); 158 wireMockRuntimeInfo.getWireMock().verifyThat(1, RequestPatternBuilder.newRequestPattern().withUrl(mapCall)); 159 final DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 160 assertNotNull(ds); 161 assertAll(() -> assertNotNull(ds.getPrimitiveById(1, OsmPrimitiveType.NODE)), 162 () -> assertNotNull(ds.getPrimitiveById(2, OsmPrimitiveType.NODE)), 163 () -> assertNotNull(ds.getPrimitiveById(3, OsmPrimitiveType.NODE)), 164 () -> assertNotNull(ds.getPrimitiveById(4, OsmPrimitiveType.NODE))); 165 } 166 167 /** 168 * Check search and zoom functionality 169 * @throws RequestHandlerBadRequestException If there is an issue with the handler 170 */ 171 @Main 172 @Projection 173 @Test 174 void testSearchAndZoom() throws RequestHandlerBadRequestException { 175 final LoadAndZoomHandler handler = newHandler(DEFAULT_BBOX_URL + "&search=id:1"); 176 assertDoesNotThrow(handler::handle); 177 syncThreads(); 178 final DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 179 Collection<OsmPrimitive> selected = ds.getSelected(); 180 assertEquals(1, selected.size()); 181 assertTrue(selected.contains(ds.getPrimitiveById(1, OsmPrimitiveType.NODE))); 182 assertTrue(ds.searchNodes(MainApplication.getMap().mapView.getRealBounds().toBBox()) 183 .contains((Node) ds.getPrimitiveById(1, OsmPrimitiveType.NODE))); 184 } 185 186 /** 187 * Check select and zoom functionality 188 * @throws RequestHandlerBadRequestException If there is an issue with the handler 189 */ 190 @Main 191 @Projection 192 @Test 193 void testSelectAndZoom() throws RequestHandlerBadRequestException { 194 final LoadAndZoomHandler handler = newHandler(DEFAULT_BBOX_URL + "&select=n1"); 195 assertDoesNotThrow(handler::handle); 196 syncThreads(); 197 final DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 198 Collection<OsmPrimitive> selected = ds.getSelected(); 199 assertEquals(1, selected.size()); 200 assertTrue(selected.contains(ds.getPrimitiveById(1, OsmPrimitiveType.NODE))); 201 assertTrue(ds.searchNodes(MainApplication.getMap().mapView.getRealBounds().toBBox()) 202 .contains((Node) ds.getPrimitiveById(1, OsmPrimitiveType.NODE))); 203 } 204 205 /** 206 * Check changeset tag functionality 207 * @throws RequestHandlerBadRequestException If there is an issue with the handler 208 */ 209 @Test 210 void testChangesetTags() throws RequestHandlerBadRequestException { 211 final String comment = "Add buildings, roads, and other random stuff"; 212 final String source = "This isn't Bing"; 213 final String hashtag = "#test-hashcodes"; 214 final String customTags = "custom=tag|is=here"; 215 final LoadAndZoomHandler handler = newHandler(DEFAULT_BBOX_URL 216 + "&changeset_comment=" + URLEncoder.encode(comment, StandardCharsets.UTF_8) 217 + "&changeset_source=" + URLEncoder.encode(source, StandardCharsets.UTF_8) 218 + "&changeset_hashtags=" + URLEncoder.encode(hashtag, StandardCharsets.UTF_8) 219 + "&changeset_tags=" + URLEncoder.encode(customTags, StandardCharsets.UTF_8)); 220 assertDoesNotThrow(handler::handle); 221 syncThreads(); 222 final DataSet ds = MainApplication.getLayerManager().getEditDataSet(); 223 assertEquals(comment, ds.getChangeSetTags().get("comment")); 224 assertEquals(source, ds.getChangeSetTags().get("source")); 225 assertEquals(hashtag, ds.getChangeSetTags().get("hashtags")); 226 assertEquals("tag", ds.getChangeSetTags().get("custom")); 227 assertEquals("here", ds.getChangeSetTags().get("is")); 62 228 } 63 229 } -
trunk/test/unit/org/openstreetmap/josm/testutils/annotations/BasicWiremock.java
r19056 r19152 3 3 4 4 import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; 5 import static org.junit.jupiter.api.Assertions.assert True;5 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 6 6 import static org.junit.jupiter.api.Assertions.fail; 7 7 … … 19 19 import java.util.Arrays; 20 20 import java.util.List; 21 import java.util.stream.Collectors; 22 21 22 import com.github.tomakehurst.wiremock.core.WireMockConfiguration; 23 import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; 23 24 import org.junit.jupiter.api.extension.AfterAllCallback; 24 import org.junit.jupiter.api.extension.AfterEachCallback;25 25 import org.junit.jupiter.api.extension.BeforeAllCallback; 26 26 import org.junit.jupiter.api.extension.BeforeEachCallback; … … 29 29 import org.junit.jupiter.api.extension.ParameterContext; 30 30 import org.junit.jupiter.api.extension.ParameterResolutionException; 31 import org.junit.jupiter.api.extension.ParameterResolver;32 31 import org.junit.platform.commons.support.AnnotationSupport; 33 32 import org.openstreetmap.josm.TestUtils; 34 33 import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 35 import org.openstreetmap.josm.gui.util.GuiHelper;36 34 import org.openstreetmap.josm.io.OsmApi; 37 35 import org.openstreetmap.josm.spi.preferences.Config; … … 43 41 import com.github.tomakehurst.wiremock.client.WireMock; 44 42 import com.github.tomakehurst.wiremock.extension.ResponseTransformerV2; 45 import com.github.tomakehurst.wiremock.verification.LoggedRequest;46 43 47 44 /** … … 81 78 * 82 79 */ 83 class WireMockExtension 84 implements AfterAllCallback, AfterEachCallback, BeforeAllCallback, BeforeEachCallback, ParameterResolver { 80 class WireMockExtension extends com.github.tomakehurst.wiremock.junit5.WireMockExtension { 81 protected WireMockExtension() { 82 this(defaultOptions()); 83 } 84 85 /** 86 * Create a new extension with options 87 * 88 * @param builder a {@link Builder} 89 * instance holding the initialisation parameters for the extension. 90 */ 91 protected WireMockExtension(Builder builder) { 92 super(builder); 93 } 94 85 95 /** 86 96 * Get the default wiremock server … … 98 108 new Pair<>(new Class<?>[] {ExtensionContext.class }, new Object[] {context }), 99 109 new Pair<>(new Class<?>[0], new Object[0]))) { 100 try { 101 Constructor<? extends ResponseTransformerV2> constructor = responseTransformer 102 .getConstructor(parameterMapping.a); 103 transformers.add(constructor.newInstance(parameterMapping.b)); 104 break; 105 } catch (ReflectiveOperationException e) { 106 fail(e); 107 } 110 Constructor<? extends ResponseTransformerV2> constructor = assertDoesNotThrow(() -> 111 responseTransformer.getConstructor(parameterMapping.a)); 112 ResponseTransformerV2 transformerV2 = assertDoesNotThrow(() -> constructor.newInstance(parameterMapping.b)); 113 transformers.add(transformerV2); 114 break; 108 115 } 109 116 } … … 114 121 } 115 122 123 static Builder defaultOptions() { 124 WireMockConfiguration options = WireMockConfiguration.options() 125 .usingFilesUnderDirectory(TestUtils.getTestDataRoot()) 126 .dynamicPort(); 127 return extensionOptions().options(options); 128 } 129 116 130 /** 117 131 * Replace URL servers with wiremock … … 120 134 * @param url The URL to fix 121 135 * @return A url that points at the wiremock server 136 * @deprecated since 19152 (not used in core; no known users) 122 137 */ 138 @Deprecated(forRemoval = true, since = "19152") 123 139 public static String replaceUrl(WireMockServer wireMockServer, String url) { 124 140 try { … … 132 148 133 149 @Override 134 public void afterAll(ExtensionContext context) throws Exception { 135 // Run in EDT to avoid stopping wiremock server before wiremock requests finish. 136 GuiHelper.runInEDTAndWait(getWiremock(context)::stop); 137 } 138 139 @Override 140 public void afterEach(ExtensionContext context) throws Exception { 141 List<LoggedRequest> missed = getWiremock(context).findUnmatchedRequests().getRequests(); 142 missed.forEach(r -> Logging.error(r.getAbsoluteUrl())); 143 try { 144 assertTrue(missed.isEmpty(), missed.stream().map(LoggedRequest::getUrl).collect(Collectors.joining("\n\n"))); 145 } finally { 146 getWiremock(context).resetRequests(); 147 getWiremock(context).resetToDefaultMappings(); 148 getWiremock(context).resetScenarios(); 149 if (AnnotationUtils.elementIsAnnotated(context.getElement(), BasicWiremock.class) 150 || getWiremock(context) == null) { 151 this.afterAll(context); 152 } 153 } 154 } 155 156 @Override 157 public void beforeAll(ExtensionContext context) throws Exception { 158 getWiremock(context).start(); 159 } 160 161 @Override 162 public void beforeEach(ExtensionContext context) throws Exception { 163 if (AnnotationUtils.elementIsAnnotated(context.getElement(), BasicWiremock.class) || getWiremock(context) == null) { 164 this.beforeAll(context); 165 } 150 protected void onBeforeAll(ExtensionContext extensionContext, WireMockRuntimeInfo wireMockRuntimeInfo) { 151 extensionContext.getStore(ExtensionContext.Namespace.create(BasicWiremock.WireMockExtension.class)) 152 .put(BasicWiremock.WireMockExtension.class, this); 153 } 154 155 @Override 156 protected void onAfterAll(ExtensionContext extensionContext, WireMockRuntimeInfo wireMockRuntimeInfo) { 157 // Sync threads to ensure that no further wiremock requests will be made 158 final ThreadSync.ThreadSyncExtension threadSyncExtension = new ThreadSync.ThreadSyncExtension(); 159 assertDoesNotThrow(() -> threadSyncExtension.afterEach(extensionContext)); 160 } 161 162 @Override 163 protected void onBeforeEach(ExtensionContext context, WireMockRuntimeInfo wireMockRuntimeInfo) { 166 164 if (context.getTestClass().isPresent()) { 167 165 List<Field> wireMockFields = AnnotationSupport.findAnnotatedFields(context.getRequiredTestClass(), BasicWiremock.class); … … 172 170 try { 173 171 field.set(context.getTestInstance().orElse(null), getWiremock(context)); 172 } catch (IllegalAccessException e) { 173 fail(e); 174 174 } finally { 175 175 field.setAccessible(isAccessible); … … 185 185 public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) 186 186 throws ParameterResolutionException { 187 if (super.supportsParameter(parameterContext, extensionContext)) { 188 return true; 189 } 190 if (WireMockRuntimeInfo.class.isAssignableFrom(parameterContext.getParameter().getType())) { 191 return true; 192 } 187 193 return parameterContext.getParameter().getAnnotation(BasicWiremock.class) != null 188 194 && parameterContext.getParameter().getType() == WireMockServer.class; … … 192 198 public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) 193 199 throws ParameterResolutionException { 200 if (super.supportsParameter(parameterContext, extensionContext)) { 201 return super.resolveParameter(parameterContext, extensionContext); 202 } 203 if (WireMockRuntimeInfo.class.isAssignableFrom(parameterContext.getParameter().getType())) { 204 return getRuntimeInfo(); 205 } 194 206 return getWiremock(extensionContext); 195 207 } … … 199 211 * A class specifically to mock OSM API calls 200 212 */ 201 class OsmApiExtension extends WireMockExtension { 202 @Override 203 public void afterAll(ExtensionContext context) throws Exception { 204 try { 205 super.afterAll(context); 206 } finally { 207 Config.getPref().put("osm-server.url", "https://invalid.url"); 208 } 213 class OsmApiExtension implements BeforeAllCallback, BeforeEachCallback, AfterAllCallback { 214 @Override 215 public void afterAll(ExtensionContext context) { 216 Config.getPref().put("osm-server.url", "https://invalid.url"); 209 217 } 210 218 … … 214 222 fail("OsmApiExtension requires @BasicPreferences"); 215 223 } 216 super.beforeAll(context); 217 Config.getPref().put("osm-server.url", getWiremock(context).baseUrl() + "/api"); 218 getWiremock(context).stubFor(WireMock.get("/api/0.6/capabilities") 224 this.beforeEach(context); 225 } 226 227 @Override 228 public void beforeEach(ExtensionContext extensionContext) throws Exception { 229 BasicWiremock.WireMockExtension extension = 230 extensionContext.getStore(ExtensionContext.Namespace.create(BasicWiremock.WireMockExtension.class)) 231 .get(BasicWiremock.WireMockExtension.class, BasicWiremock.WireMockExtension.class); 232 WireMockRuntimeInfo wireMockRuntimeInfo = extension.getRuntimeInfo(); 233 Config.getPref().put("osm-server.url", wireMockRuntimeInfo.getHttpBaseUrl() + "/api"); 234 wireMockRuntimeInfo.getWireMock().register(WireMock.get("/api/0.6/capabilities") 219 235 .willReturn(WireMock.aResponse().withBodyFile("api/0.6/capabilities"))); 220 getWiremock(context).stubFor(WireMock.get("/api/capabilities")236 wireMockRuntimeInfo.getWireMock().register(WireMock.get("/api/capabilities") 221 237 .willReturn(WireMock.aResponse().withBodyFile("api/capabilities"))); 222 OsmApi.getOsmApi().initialize(NullProgressMonitor.INSTANCE);238 assertDoesNotThrow(() -> OsmApi.getOsmApi().initialize(NullProgressMonitor.INSTANCE)); 223 239 } 224 240 }
Note:
See TracChangeset
for help on using the changeset viewer.