Changeset 19152 in josm for trunk


Ignore:
Timestamp:
2024-07-25T20:56:22+02:00 (4 months ago)
Author:
taylor.smock
Message:

See #23821: Refactor LoadAndZoomHandler and add tests

This fixes some lint issues, specifically in regard to method complexity.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/LoadAndZoomHandler.java

    r18871 r19152  
    66import java.awt.geom.Area;
    77import java.awt.geom.Rectangle2D;
    8 import java.util.Arrays;
    98import java.util.Collection;
    109import java.util.Collections;
     
    1817import java.util.concurrent.TimeoutException;
    1918import java.util.stream.Collectors;
     19import java.util.stream.Stream;
    2020
    2121import javax.swing.JOptionPane;
     
    6565    public static final String command2 = "zoom";
    6666    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";
    6774
    6875    // Mandatory arguments
     
    8188        String msg = tr("Remote Control has been asked to load data from the API.") +
    8289                "<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()) {
    8491            msg += "<br>" + tr("Selection: {0}", toSelect.size());
    8592        }
     
    94101    @Override
    95102    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"};
    99106    }
    100107
     
    127134    @Override
    128135    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 {
    129162        DownloadOsmTask osmTask = new DownloadOsmTask();
    130163        try {
     
    137170                    Area toDownload = null;
    138171                    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();
    157173                    }
    158174                    if (toDownload != null && toDownload.isEmpty()) {
    159175                        Logging.info("RemoteControl: no download necessary");
    160176                    } 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);
    189178                    }
    190179                }
     
    195184            throw new RequestHandlerErrorException(ex);
    196185        }
    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 {
    210250        final Collection<OsmPrimitive> forTagAdd = new LinkedHashSet<>();
    211251        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()) {
    213253            // 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);
    255257        } else {
    256258            // 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));
    265295            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);
    280301            });
    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)) {
    285332            // needs to run in EDT since forTagAdd is updated in EDT as well
    286333            GuiHelper.executeByMainWorkerInEDT(() -> {
     
    290337                    new Notification(isKeepingCurrentSelection
    291338                            ? tr("You clicked on a JOSM remotecontrol link that would apply tags onto selected objects.\n"
    292                                     + "Since no objects have been selected before this click, no tags were added.\n"
    293                                     + "Select one or more objects and click the link again.")
     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.")
    294341                            : tr("You clicked on a JOSM remotecontrol link that would apply tags onto objects.\n"
    295                                     + "Unfortunately that link seems to be broken.\n"
    296                                     + "Technical explanation: the URL query parameter ''select='' or ''search='' has an invalid value.\n"
    297                                     + "Ask someone at the origin of the clicked link to fix this.")
    298                         ).setIcon(JOptionPane.WARNING_MESSAGE).setDuration(Notification.TIME_LONG).show();
     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();
    299346                }
    300347            });
     
    303350
    304351    static void parseChangesetTags(Map<String, String> args) {
    305         if (args.containsKey("changeset_tags")) {
     352        if (args.containsKey(CHANGESET_TAGS)) {
    306353            MainApplication.worker.submit(() -> {
    307354                DataSet ds = MainApplication.getLayerManager().getEditDataSet();
    308355                if (ds != null) {
    309                     AddTagsDialog.parseUrlTagsToKeyValues(args.get("changeset_tags")).forEach(ds::addChangeSetTag);
     356                    AddTagsDialog.parseUrlTagsToKeyValues(args.get(CHANGESET_TAGS)).forEach(ds::addChangeSetTag);
    310357                }
    311358            });
     
    366413
    367414        // 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)) {
    369420            toSelect.clear();
    370             for (String item : args.get("select").split(",", -1)) {
     421            for (String item : args.get(SELECT).split(",", -1)) {
    371422                if (!item.isEmpty()) {
    372423                    if (CURRENT_SELECTION.equalsIgnoreCase(item)) {
  • trunk/test/unit/org/openstreetmap/josm/io/remotecontrol/handler/LoadAndZoomHandlerTest.java

    r18037 r19152  
    22package org.openstreetmap.josm.io.remotecontrol.handler;
    33
     4import static org.junit.jupiter.api.Assertions.assertAll;
    45import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
    56import static org.junit.jupiter.api.Assertions.assertEquals;
     7import static org.junit.jupiter.api.Assertions.assertNotNull;
     8import static org.junit.jupiter.api.Assertions.assertNull;
    69import static org.junit.jupiter.api.Assertions.assertThrows;
    7 
     10import static org.junit.jupiter.api.Assertions.assertTrue;
     11
     12import java.net.URLEncoder;
     13import java.nio.charset.StandardCharsets;
     14import java.util.Collection;
     15
     16import com.github.tomakehurst.wiremock.client.WireMock;
     17import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
     18import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
     19import org.junit.jupiter.api.BeforeEach;
     20import org.junit.jupiter.api.extension.ExtendWith;
     21import org.openstreetmap.josm.data.osm.DataSet;
     22import org.openstreetmap.josm.data.osm.Node;
     23import org.openstreetmap.josm.data.osm.OsmPrimitive;
     24import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     25import org.openstreetmap.josm.gui.MainApplication;
    826import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerBadRequestException;
    927import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
    1028
    1129import org.junit.jupiter.api.Test;
     30import org.openstreetmap.josm.testutils.annotations.BasicWiremock;
     31import org.openstreetmap.josm.testutils.annotations.Main;
     32import org.openstreetmap.josm.testutils.annotations.Projection;
     33import org.openstreetmap.josm.testutils.annotations.ThreadSync;
    1234
    1335/**
    1436 * Unit tests of {@link LoadAndZoomHandler} class.
    1537 */
     38@BasicPreferences
     39@BasicWiremock
     40@ExtendWith(BasicWiremock.OsmApiExtension.class)
    1641class LoadAndZoomHandlerTest {
     42    private static final String DEFAULT_BBOX_URL = "https://localhost/load_and_zoom?left=0&bottom=0&right=0.001&top=0.001";
    1743    private static LoadAndZoomHandler newHandler(String url) throws RequestHandlerBadRequestException {
    1844        LoadAndZoomHandler req = new LoadAndZoomHandler();
     45        req.myCommand = LoadAndZoomHandler.command;
    1946        if (url != null)
    2047            req.setUrl(url);
     
    2249    }
    2350
     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
    2469    /**
    2570     * Unit test for bad request - no param.
    26      * @throws Exception if any error occurs
    27      */
    28     @Test
    29     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);
    3176        assertEquals("NumberFormatException (empty String)", e.getMessage());
    3277    }
     
    3479    /**
    3580     * Unit test for bad request - invalid URL.
    36      * @throws Exception if any error occurs
    37      */
    38     @Test
    39     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);
    4186        assertEquals("The following keys are mandatory, but have not been provided: bottom, top, left, right", e.getMessage());
    4287    }
     
    4489    /**
    4590     * Unit test for bad request - incomplete URL.
    46      * @throws Exception if any error occurs
    47      */
    48     @Test
    49     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);
    5196        assertEquals("The following keys are mandatory, but have not been provided: bottom, top, left, right", e.getMessage());
    5297    }
    5398
    5499    /**
    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"));
    62228    }
    63229}
  • trunk/test/unit/org/openstreetmap/josm/testutils/annotations/BasicWiremock.java

    r19056 r19152  
    33
    44import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
    5 import static org.junit.jupiter.api.Assertions.assertTrue;
     5import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
    66import static org.junit.jupiter.api.Assertions.fail;
    77
     
    1919import java.util.Arrays;
    2020import java.util.List;
    21 import java.util.stream.Collectors;
    22 
     21
     22import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
     23import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
    2324import org.junit.jupiter.api.extension.AfterAllCallback;
    24 import org.junit.jupiter.api.extension.AfterEachCallback;
    2525import org.junit.jupiter.api.extension.BeforeAllCallback;
    2626import org.junit.jupiter.api.extension.BeforeEachCallback;
     
    2929import org.junit.jupiter.api.extension.ParameterContext;
    3030import org.junit.jupiter.api.extension.ParameterResolutionException;
    31 import org.junit.jupiter.api.extension.ParameterResolver;
    3231import org.junit.platform.commons.support.AnnotationSupport;
    3332import org.openstreetmap.josm.TestUtils;
    3433import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
    35 import org.openstreetmap.josm.gui.util.GuiHelper;
    3634import org.openstreetmap.josm.io.OsmApi;
    3735import org.openstreetmap.josm.spi.preferences.Config;
     
    4341import com.github.tomakehurst.wiremock.client.WireMock;
    4442import com.github.tomakehurst.wiremock.extension.ResponseTransformerV2;
    45 import com.github.tomakehurst.wiremock.verification.LoggedRequest;
    4643
    4744/**
     
    8178     *
    8279     */
    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
    8595        /**
    8696         * Get the default wiremock server
     
    98108                            new Pair<>(new Class<?>[] {ExtensionContext.class }, new Object[] {context }),
    99109                            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;
    108115                    }
    109116                }
     
    114121        }
    115122
     123        static Builder defaultOptions() {
     124            WireMockConfiguration options = WireMockConfiguration.options()
     125                    .usingFilesUnderDirectory(TestUtils.getTestDataRoot())
     126                    .dynamicPort();
     127            return extensionOptions().options(options);
     128        }
     129
    116130        /**
    117131         * Replace URL servers with wiremock
     
    120134         * @param url            The URL to fix
    121135         * @return A url that points at the wiremock server
     136         * @deprecated since 19152 (not used in core; no known users)
    122137         */
     138        @Deprecated(forRemoval = true, since = "19152")
    123139        public static String replaceUrl(WireMockServer wireMockServer, String url) {
    124140            try {
     
    132148
    133149        @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) {
    166164            if (context.getTestClass().isPresent()) {
    167165                List<Field> wireMockFields = AnnotationSupport.findAnnotatedFields(context.getRequiredTestClass(), BasicWiremock.class);
     
    172170                        try {
    173171                            field.set(context.getTestInstance().orElse(null), getWiremock(context));
     172                        } catch (IllegalAccessException e) {
     173                            fail(e);
    174174                        } finally {
    175175                            field.setAccessible(isAccessible);
     
    185185        public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
    186186                throws ParameterResolutionException {
     187            if (super.supportsParameter(parameterContext, extensionContext)) {
     188                return true;
     189            }
     190            if (WireMockRuntimeInfo.class.isAssignableFrom(parameterContext.getParameter().getType())) {
     191                return true;
     192            }
    187193            return parameterContext.getParameter().getAnnotation(BasicWiremock.class) != null
    188194                    && parameterContext.getParameter().getType() == WireMockServer.class;
     
    192198        public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
    193199                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            }
    194206            return getWiremock(extensionContext);
    195207        }
     
    199211     * A class specifically to mock OSM API calls
    200212     */
    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");
    209217        }
    210218
     
    214222                fail("OsmApiExtension requires @BasicPreferences");
    215223            }
    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")
    219235                    .willReturn(WireMock.aResponse().withBodyFile("api/0.6/capabilities")));
    220             getWiremock(context).stubFor(WireMock.get("/api/capabilities")
     236            wireMockRuntimeInfo.getWireMock().register(WireMock.get("/api/capabilities")
    221237                    .willReturn(WireMock.aResponse().withBodyFile("api/capabilities")));
    222             OsmApi.getOsmApi().initialize(NullProgressMonitor.INSTANCE);
     238            assertDoesNotThrow(() -> OsmApi.getOsmApi().initialize(NullProgressMonitor.INSTANCE));
    223239        }
    224240    }
Note: See TracChangeset for help on using the changeset viewer.