From 8a91eb33ba1e4a324e7c38036db363dfaf055788 Mon Sep 17 00:00:00 2001 From: mm-ciub <68378600+mm-ciub@users.noreply.github.com> Date: Fri, 23 Oct 2020 21:15:35 +0300 Subject: [PATCH] Invalid tags check highway inconsistency (#397) * Highway inconsistency extension and specific instructions - added filters that look for inconsistency in the usage of the highway tag (issue #377) - made necessary changes so that when passing filters, specific instructions can be passed as well * Testing and documentation update - updated documentation to reflect the addition of specific instructions - updated highway inconsistency instructions - minor edits: removed magic number, fixed default instructions and their usage - added tests for the new cases * Highway inconsistency filter fix - fixed taggable filters to reflect the expected results * Removed code smell in read config method - split the readConfigurationFilter method in two, one for simple and one for regex filters --- config/configuration.json | 3 +- docs/checks/invalidTagsCheck.md | 16 +- .../validation/tag/InvalidTagsCheck.java | 190 +++++++++++------- .../tag/invalid-tags-check-filter.json | 4 - ...ags-check-inconsistent-highway-filter.json | 18 ++ .../tag/invalid-tags-check-regex-filter.json | 3 +- .../checks/validation/tag/invalidTags.txt | 6 +- .../validation/tag/InvalidTagsCheckTest.java | 53 +++++ .../tag/InvalidTagsCheckTestRule.java | 11 + 9 files changed, 220 insertions(+), 84 deletions(-) delete mode 100644 src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalid-tags-check-filter.json create mode 100644 src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalid-tags-check-inconsistent-highway-filter.json diff --git a/config/configuration.json b/config/configuration.json index 72f1960b7..1985c565f 100644 --- a/config/configuration.json +++ b/config/configuration.json @@ -441,7 +441,8 @@ "filters.classes.tags": [ ["area","boundary->protected_area&protect_class->!"], ["relation","boundary->protected_area&protect_class->!"], - ["edge","highway->motorway,trunk,primary,secondary,tertiary,unclassified,residential,service,motorway_link,trunk_link,primary_link,secondary_link,tertiary_link,living_street,track&junction->roundabout&area->*"] + ["edge","highway->motorway,trunk,primary,secondary,tertiary,unclassified,residential,service,motorway_link,trunk_link,primary_link,secondary_link,tertiary_link,living_street,track&junction->roundabout&area->*"], + ["node","highway->emergency_access_point&ref->!", "The element is an emergency_access_point with no ref tag."] ], "challenge": { "description": "Tasks containing features with tags containing missing, conflicting, incorrect or illegal values", diff --git a/docs/checks/invalidTagsCheck.md b/docs/checks/invalidTagsCheck.md index af82f7627..13bd6e0da 100644 --- a/docs/checks/invalidTagsCheck.md +++ b/docs/checks/invalidTagsCheck.md @@ -11,10 +11,10 @@ both the configurable filters and filters passed through the resource files will Filters for this check are either passed through config or/and through resource files. File "invalidTags.txt" contains the mapping of AtlasEntity to its corresponding resource file. -Each configurable filter has 2 parts. The first is AtlasEntity class (node, edge, area, etc.). The second is a +Each configurable filter has 2 mandatory parts. The first is AtlasEntity class (node, edge, area, etc.). The second is a [TaggableFilter](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/tags/filters/TaggableFilter.java) or a [RegexTaggableFilter](https://github.com/osmlab/atlas/blob/dev/src/main/java/org/openstreetmap/atlas/tags/filters/RegexTaggableFilter.java). - +Optionally, a specific instruction string can be passed as the third part. If a feature is one of the classes given and passes the filter then it is flagged. ** Configurable Filter Example:** @@ -28,6 +28,8 @@ Filters to flag this would look like the following: The first string is the AtlasEntity class we want to look for. The second string is the taggable filter that is looking for the combination of `boundary=protected_area` without a `protect_class` tag. +For a specific instruction, the filter would look like this: +`["area,"boundary->protected_area&protect_class->!","Area missing protected_class tag."]` This would flag an osm feature like the following: [Way 673787307](https://www.openstreetmap.org/way/673787307). @@ -39,7 +41,9 @@ The RegexTaggableFilter can be configured in two ways. First, the inline configu ["node", ["source"],["illegal value regex"]] ]` The first string is the AtlasEntity. The next array of string represents the tag names for which the value must be checked. The last -array of strings represents the regex patterns that are matched with the tag values. +array of strings represents the regex patterns that are matched with the tag values. Optionally, after the regex array a specific +instruction can be passed: `["node", ["source"],["illegal value regex"], "The element has an illegal source."]` + The second option is to create the filter through a resource file. This is a more complete option allowing also the configuration of an exception map containing certain tag-value pairs that are excepted by the regex match. For this, the filter is passed in json format: `{ @@ -55,10 +59,12 @@ in json format: `{ "tagName" : "source", "values": ["open version map", "public map"] } - ] + ], + "instruction" : "The element has an illegal source." } ] - }` + }` +The `instruction` attribute is optional. To be mentioned here that the resource file for the `RegexTaggableFilter` must contain the word `regex` in it's name. #### Code Review diff --git a/src/main/java/org/openstreetmap/atlas/checks/validation/tag/InvalidTagsCheck.java b/src/main/java/org/openstreetmap/atlas/checks/validation/tag/InvalidTagsCheck.java index 9860546a3..bc84131aa 100644 --- a/src/main/java/org/openstreetmap/atlas/checks/validation/tag/InvalidTagsCheck.java +++ b/src/main/java/org/openstreetmap/atlas/checks/validation/tag/InvalidTagsCheck.java @@ -12,6 +12,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; @@ -55,39 +56,50 @@ public class InvalidTagsCheck extends BaseCheck { private static final long serialVersionUID = 5150282147895785829L; - private static final List FALLBACK_INSTRUCTIONS = Arrays.asList( - "OSM feature {0,number,#} has invalid tags.", - "Check the following tags for missing, conflicting, or incorrect values: {0}"); private static final String KEY_VALUE_SEPARATOR = "->"; private static final String DEFAULT_FILTER_RESOURCE = "invalidTags.txt"; + private static final List FALLBACK_INSTRUCTIONS = new ArrayList<>(); + private static final String DEFAULT_NR_TAGS_INSTRUCTION = "OSM feature {0,number,#} has invalid tags."; + private static final String DEFAULT_INSTRUCTION = "Check the following tags for missing, conflicting, or incorrect values: {0}"; private static final Logger logger = LoggerFactory.getLogger(InvalidTagsCheck.class); public static final int INLINE_REGEX_FILTER_SIZE = 3; private static final String REGEX = "regex"; + private static final int REGEX_INSTRUCTION_INDEX = 3; - private final List, List>>> classTagFilters; + private final List, List, Integer>>>> classTagFilters; + + private static int addInstruction(final String instruction) + { + if (Objects.nonNull(instruction) && !instruction.isEmpty()) + { + FALLBACK_INSTRUCTIONS.add(instruction); + return FALLBACK_INSTRUCTIONS.size() - 1; + } + return 1; + } /** - * @return a List of Tuple containing AtlasEntity and a list of both TaggableFilters and - * RegexTaggableFilters read from the json files of each AtlasEntity. - * DEFAULT_FILTER_RESOURCE file maps each AtlasEntity to its corresponding filter files. - * The RegexTaggableFilter file must contain the word regex in it's naming. ex. - * bad-source-regex-filter.json + * @return a List of Tuple containing AtlasEntity and a list of Tuple containing either a + * TaggableFilters or a RegexTaggableFilters with the index of the associated + * instruction read from the json files of each AtlasEntity. DEFAULT_FILTER_RESOURCE + * file maps each AtlasEntity to its corresponding filter files. The RegexTaggableFilter + * file must contain the word regex in it's naming. ex. bad-source-regex-filter.json */ - private static List, List>>> getDefaultFilters() + private static List, List, Integer>>>> getDefaultFilters() { try (BufferedReader reader = new BufferedReader(new InputStreamReader( InvalidTagsCheck.class.getResourceAsStream(DEFAULT_FILTER_RESOURCE)))) { - final List, List>>> listOfClassToFilters = new ArrayList<>(); + final List, List, Integer>>>> listOfClassToFilters = new ArrayList<>(); String line; while ((line = reader.readLine()) != null) { final String[] split = line.split(COLON); - if (split.length == 2) + if (split.length >= 2) { - final List> filters = split[1].contains(REGEX) - ? getRegexFiltersFromResource(split[1]) - : getFiltersFromResource(split[1]); + final List, Integer>> filters = split[1] + .contains(REGEX) ? getRegexFiltersFromResource(split[1]) + : getFiltersFromResource(split[1]); listOfClassToFilters.add(new Tuple<>( ItemType.valueOf(split[0].toUpperCase()).getMemberClass(), filters)); } @@ -116,32 +128,51 @@ private static Set getFilterKeys(final TaggableFilter filter) } /** - * Read the json file and return a list of TaggableFilter for each line. + * Read the json file and return a list of TaggableFilter associated with instructions for each + * line. * * @param filterResourcePath * file path - * @return list of TaggableFilter + * @return list of Tuple containing TaggableFilter and instruction */ - private static List getFiltersFromResource(final String filterResourcePath) + private static List, Integer>> getFiltersFromResource( + final String filterResourcePath) { try (InputStreamReader reader = new InputStreamReader( InvalidTagsCheck.class.getResourceAsStream(filterResourcePath))) { + final List, Integer>> result = new ArrayList<>(); final JsonElement element = new JsonParser().parse(reader); final JsonArray filters = element.getAsJsonObject().get("filters").getAsJsonArray(); - return StreamSupport.stream(filters.spliterator(), false) + final List taggableFilters = StreamSupport + .stream(filters.spliterator(), false) .map(jsonElement -> TaggableFilter.forDefinition(jsonElement.getAsString())) .collect(Collectors.toList()); + final JsonArray instructionsArray = element.getAsJsonObject().get("instructions") + .getAsJsonArray(); + final List instructions = StreamSupport + .stream(instructionsArray.spliterator(), false).map(JsonElement::getAsString) + .collect(Collectors.toList()); + for (int i = 0; i < taggableFilters.size(); i++) + { + final String instruction = i < instructions.size() ? instructions.get(i) : ""; + final int instructionIndex = addInstruction(instruction); + result.add(Tuple.createTuple(taggableFilters.get(i), instructionIndex)); + } + return result; + } catch (final Exception exception) { - logger.error("There was a problem parsing invalid-tags-check-filter.json. " - + "Check if the JSON file has valid structure.", exception); + logger.error( + "There was a problem parsing invalid-tags-check-inconsistent-highway-filter.json. " + + "Check if the JSON file has valid structure.", + exception); return Collections.emptyList(); } } - private static List getRegexFiltersFromResource( + private static List, Integer>> getRegexFiltersFromResource( final String filterResourcePath) { try (InputStreamReader reader = new InputStreamReader( @@ -166,7 +197,11 @@ private static List getRegexFiltersFromResource( exception.getAsJsonArray("values")); exceptions.putIfAbsent(tagName, values); }); - return new RegexTaggableFilter(tagNames, regex, exceptions); + final RegexTaggableFilter filter = new RegexTaggableFilter(tagNames, regex, + exceptions); + final String instruction = jsonObject.get("instruction").getAsString(); + final int instructionIndex = addInstruction(instruction); + return Tuple.createTuple(filter, instructionIndex); }).collect(Collectors.toList()); } catch (final Exception exception) @@ -193,14 +228,18 @@ private static Set getSetFromJsonArray(final JsonArray array) * A {@link String} of comma delimited {@link AtlasEntity} class names * @param tagFilterString * A {@link String} {@link TaggableFilter} definition + * @param instruction + * A {@link String} representing a specific instruction for the filter * @return A {@link Tuple} of a {@link Set} of {@link AtlasEntity} {@link Class}es and a * {@link TaggableFilter} */ - private static Tuple, List> stringsToClassTagFilter( - final String classString, final String tagFilterString) + private static Tuple, List>> stringsToClassTagFilter( + final String classString, final String tagFilterString, final String instruction) { - final List filters = new ArrayList<>(); - filters.add(TaggableFilter.forDefinition(tagFilterString)); + final int instructionIndex = addInstruction(instruction); + final List> filters = new ArrayList<>(); + filters.add( + Tuple.createTuple(TaggableFilter.forDefinition(tagFilterString), instructionIndex)); return Tuple.createTuple(ItemType.valueOf(classString.toUpperCase()).getMemberClass(), filters); } @@ -216,6 +255,8 @@ private static Tuple, List> strings public InvalidTagsCheck(final Configuration configuration) { super(configuration); + FALLBACK_INSTRUCTIONS.add(DEFAULT_NR_TAGS_INSTRUCTION); + FALLBACK_INSTRUCTIONS.add(DEFAULT_INSTRUCTION); final boolean overrideResourceFilters = this.configurationValue(configuration, "filters.resource.override", false); // If the "filters.resource.override" key in the config is set to true, use only the filters @@ -228,7 +269,7 @@ public InvalidTagsCheck(final Configuration configuration) // is set to true else { - final List, List>>> defaultFilters = getDefaultFilters(); + final List, List, Integer>>>> defaultFilters = getDefaultFilters(); // Add all filters from the config file to the default list of filters defaultFilters.addAll(this.getFiltersFromConfiguration(configuration)); this.classTagFilters = defaultFilters; @@ -263,19 +304,19 @@ protected Optional flag(final AtlasObject object) // Test that the object is one of the given AtlasEntity classes .filter(classTagFilter -> classTagFilter.getFirst().isInstance(object)) .map(Tuple::getSecond).flatMap(Collection::stream) - .filter(filter -> filter.test(object)) + .filter(filter -> filter.getFirst().test(object)) // Map the filters that were passed to instructions .map(filter -> { - if (filter instanceof TaggableFilter) + if (filter.getFirst() instanceof TaggableFilter) { - return this.getLocalizedInstruction(1, - getFilterKeys((TaggableFilter) filter)); + return this.getLocalizedInstruction(filter.getSecond(), + getFilterKeys((TaggableFilter) filter.getFirst())); } else { - return this.getLocalizedInstruction(1, - ((RegexTaggableFilter) filter).getMatchedTags(object)); + return this.getLocalizedInstruction(filter.getSecond(), + ((RegexTaggableFilter) filter.getFirst()).getMatchedTags(object)); } }).collect(Collectors.toList()); if (!instructions.isEmpty()) @@ -307,62 +348,75 @@ protected List getFallbackInstructions() /** * From the config file, create a list of Tuples with atlas entity and corresponding list of - * taggable and regex filters + * taggable and regex filters associated with instructions * * @param configuration * configuration * @return List of Tuples containing AtlasEntity and its corresponding list of TaggableFilter * and RegexTaggableFilter */ - private List, List>>> getFiltersFromConfiguration( + private List, List, Integer>>>> getFiltersFromConfiguration( final Configuration configuration) { - final List, List>>> filters = this - .readConfigurationFilter(configuration, "filters.classes.tags"); - final List, List>>> allFilters = new ArrayList<>( + final List, List, Integer>>>> filters = this + .readConfigurationFilter(configuration); + final List, List, Integer>>>> allFilters = new ArrayList<>( filters); - final List, List>>> regexFilters = this - .readConfigurationFilter(configuration, "filters.classes.regex"); + final List, List, Integer>>>> regexFilters = this + .readRegexConfigurationFilter(configuration); allFilters.addAll(regexFilters); return allFilters; } @SuppressWarnings("unchecked") - private List, List>>> readConfigurationFilter( - final Configuration configuration, final String key) + private List, List, Integer>>>> readConfigurationFilter( + final Configuration configuration) { - return this.configurationValue(configuration, key, Collections.emptyList(), - configList -> configList.stream().map(classTagValue -> + return this.configurationValue(configuration, "filters.classes.tags", + Collections.emptyList(), configList -> configList.stream().map(classTagValue -> { - - if (key.contains(REGEX)) + final List classTagList = (List) classTagValue; + if (classTagList.size() > 1) { - final List classTagList = (List) classTagValue; - if (classTagList.size() == INLINE_REGEX_FILTER_SIZE) - { - final String element = (String) classTagList.get(0); - final List tagNames = (List) classTagList.get(1); - final List regex = (List) classTagList.get(2); - final List filters = new ArrayList<>(); - filters.add(new RegexTaggableFilter(new HashSet<>(tagNames), - new HashSet<>(regex), null)); - return Optional.of(Tuple.createTuple( - ItemType.valueOf(element.toUpperCase()).getMemberClass(), - filters)); - } + final String instruction = classTagList.size() > 2 ? classTagList.get(2) + : ""; + return Optional.of(stringsToClassTagFilter(classTagList.get(0), + classTagList.get(1), instruction)); } - else + return Optional.empty(); + }).filter(Optional::isPresent).map( + tuple -> (Tuple, List, Integer>>>) tuple + .get()) + .collect(Collectors.toList())); + } + + @SuppressWarnings("unchecked") + private List, List, Integer>>>> readRegexConfigurationFilter( + final Configuration configuration) + { + return this.configurationValue(configuration, "filters.classes.regex", + Collections.emptyList(), configList -> configList.stream().map(classTagValue -> + { + final List classTagList = (List) classTagValue; + if (classTagList.size() >= INLINE_REGEX_FILTER_SIZE) { - final List classTagList = (List) classTagValue; - if (classTagList.size() > 1) - { - return Optional.of(stringsToClassTagFilter(classTagList.get(0), - classTagList.get(1))); - } + final String element = (String) classTagList.get(0); + final List tagNames = (List) classTagList.get(1); + final List regex = (List) classTagList.get(2); + final List, Integer>> filters = new ArrayList<>(); + final String instruction = classTagList.size() > INLINE_REGEX_FILTER_SIZE + ? (String) classTagList.get(REGEX_INSTRUCTION_INDEX) + : ""; + final RegexTaggableFilter filter = new RegexTaggableFilter( + new HashSet<>(tagNames), new HashSet<>(regex), null); + final int instructionIndex = addInstruction(instruction); + filters.add(Tuple.createTuple(filter, instructionIndex)); + return Optional.of(Tuple.createTuple( + ItemType.valueOf(element.toUpperCase()).getMemberClass(), filters)); } return Optional.empty(); }).filter(Optional::isPresent).map( - tuple -> (Tuple, List>>) tuple + tuple -> (Tuple, List, Integer>>>) tuple .get()) .collect(Collectors.toList())); } diff --git a/src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalid-tags-check-filter.json b/src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalid-tags-check-filter.json deleted file mode 100644 index 63ef9d457..000000000 --- a/src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalid-tags-check-filter.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "filters": [ - ] -} diff --git a/src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalid-tags-check-inconsistent-highway-filter.json b/src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalid-tags-check-inconsistent-highway-filter.json new file mode 100644 index 000000000..0239b33cc --- /dev/null +++ b/src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalid-tags-check-inconsistent-highway-filter.json @@ -0,0 +1,18 @@ +{ + "filters": [ + "junction->*&junction->!yes&highway->!", + "oneway->*&highway->!&railway->!&aerialway->!&waterway->!&aeroway->!&piste:type->!", + "highway->!&disused:highway->!&abandoned:highway->!&construction:highway->!&proposed:highway->!&planned:highway->!&leisure->!track&tracktype->*||lanes->*", + "highway->emergency_access_point&ref->!", + "footway->sidewalk&highway->!footway&highway->!path&highway->!construction", + "highway->motorway&ref->!&nat_ref->!&int_ref->!&noref->!" + ], + "instructions": [ + "The element has a junction tag but no highway tag.", + "The element has a oneway tag but no highway tag.", + "The element is missing a highway tag.", + "The element is an emergency access point but has no ref tag.", + "The element is a footway=sidewalk but has no appropriate highway tag.", + "The element is a motorway but has no ref tag." + ] +} diff --git a/src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalid-tags-check-regex-filter.json b/src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalid-tags-check-regex-filter.json index 2cadda406..0db7374aa 100644 --- a/src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalid-tags-check-regex-filter.json +++ b/src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalid-tags-check-regex-filter.json @@ -8,7 +8,8 @@ ".*(?i)\\bvworld\\b.*", ".*(?i)\\bxdworld\\b.*" ], - "exceptions":[] + "exceptions":[], + "instruction": "The following element has an illegal source." } ] } \ No newline at end of file diff --git a/src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalidTags.txt b/src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalidTags.txt index a6afa0812..22894983d 100644 --- a/src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalidTags.txt +++ b/src/main/resources/org/openstreetmap/atlas/checks/validation/tag/invalidTags.txt @@ -1,8 +1,4 @@ -area:invalid-tags-check-filter.json -relation:invalid-tags-check-filter.json -line:invalid-tags-check-filter.json -point:invalid-tags-check-filter.json -node:invalid-tags-check-filter.json +edge:invalid-tags-check-inconsistent-highway-filter.json area:invalid-tags-check-regex-filter.json relation:invalid-tags-check-regex-filter.json line:invalid-tags-check-regex-filter.json diff --git a/src/test/java/org/openstreetmap/atlas/checks/validation/tag/InvalidTagsCheckTest.java b/src/test/java/org/openstreetmap/atlas/checks/validation/tag/InvalidTagsCheckTest.java index f47a0d937..d03a8a508 100644 --- a/src/test/java/org/openstreetmap/atlas/checks/validation/tag/InvalidTagsCheckTest.java +++ b/src/test/java/org/openstreetmap/atlas/checks/validation/tag/InvalidTagsCheckTest.java @@ -29,6 +29,31 @@ public void appendConfigFiltersTest() this.verifier.globallyVerify(flags -> Assert.assertEquals(2, flags.size())); } + @Test + public void configRegexInstruction() + { + this.verifier.actual(this.setup.getIllegalSourceLinkNode(), + new InvalidTagsCheck(ConfigurationResolver.inlineConfiguration( + "{\"InvalidTagsCheck\":{\"filters.resource.override\": true,\"filters.classes.regex\": [" + + " [\"node\", [\"source\"],[\".*(?i)\\\\bgoogle\\\\b.*\", \".*(?i)\\\\bhere\\\\b(?=.*map|.com)\",\n" + + " \".*(?i)\\\\bvworld\\\\b.*\", \".*(?i)\\\\bxdworld\\\\b.*\"], \"Illegal tag: {0}.\"]" + + " ]}}"))); + this.verifier.globallyVerify(flags -> Assert.assertEquals( + "1. OSM feature 1 has invalid tags.\n" + "2. Illegal tag: source.", + flags.get(0).getInstructions())); + } + + @Test + public void configTaggableInstruction() + { + this.verifier.actual(this.setup.testAtlas(), + new InvalidTagsCheck(ConfigurationResolver.inlineConfiguration( + "{\"InvalidTagsCheck\":{\"filters.resource.override\": true,\"filters.classes.tags\":[[\"edge\",\"route->ferry&highway->*\",\"The feature has tag issues.\"]]}}"))); + this.verifier.globallyVerify(flags -> Assert.assertEquals( + "1. OSM feature 5 has invalid tags.\n" + "2. The feature has tag issues.", + flags.get(0).getInstructions())); + } + @Test public void illegalSourceEdge() { @@ -117,6 +142,34 @@ public void invalidRoundaboutAreaTest() flags.get(0).getInstructions())); } + @Test + public void resourceRegexInstruction() + { + this.verifier.actual(this.setup.getIllegalSourceLinkNode(), + new InvalidTagsCheck(ConfigurationResolver.inlineConfiguration( + "{\"InvalidTagsCheck\":{\"filters.resource.override\": false}}"))); + this.verifier.globallyVerify(flags -> Assert.assertEquals( + "1. OSM feature 1 has invalid tags.\n" + + "2. The following element has an illegal source.", + flags.get(0).getInstructions())); + this.verifier.globallyVerify(flags -> Assert.assertEquals( + "1. OSM feature 2 has invalid tags.\n" + + "2. The following element has an illegal source.", + flags.get(1).getInstructions())); + } + + @Test + public void resourceTaggableInstruction() + { + this.verifier.actual(this.setup.getInconsistentHighwayAtlas(), + new InvalidTagsCheck(ConfigurationResolver.inlineConfiguration( + "{\"InvalidTagsCheck\":{\"filters.resource.override\": false}}"))); + this.verifier.globallyVerify(flags -> Assert.assertEquals( + "1. OSM feature 1001 has invalid tags.\n" + + "2. The element has a junction tag but no highway tag.", + flags.get(0).getInstructions())); + } + @Test public void validEmptyConfigTest() { diff --git a/src/test/java/org/openstreetmap/atlas/checks/validation/tag/InvalidTagsCheckTestRule.java b/src/test/java/org/openstreetmap/atlas/checks/validation/tag/InvalidTagsCheckTestRule.java index b9a9e3d47..34e80d1c0 100644 --- a/src/test/java/org/openstreetmap/atlas/checks/validation/tag/InvalidTagsCheckTestRule.java +++ b/src/test/java/org/openstreetmap/atlas/checks/validation/tag/InvalidTagsCheckTestRule.java @@ -40,6 +40,12 @@ public class InvalidTagsCheckTestRule extends CoreTestRule @Loc(value = TEST_2) }) }) private Atlas illegalSourceLinkNode; + @TestAtlas(nodes = { @Node(id = "1000000", coordinates = @Loc(value = TEST_1)), + @Node(id = "2000000", coordinates = @Loc(value = TEST_2)) }, edges = { + @Edge(id = "1001000001", coordinates = { @Loc(value = TEST_1), + @Loc(value = TEST_2) }, tags = { "junction=roundabout" }) }) + private Atlas inconsistentHighwayAtlas; + @TestAtlas( // nodes nodes = { @@ -86,6 +92,11 @@ public Atlas getIllegalSourceLinkNode() return this.illegalSourceLinkNode; } + public Atlas getInconsistentHighwayAtlas() + { + return this.inconsistentHighwayAtlas; + } + public Atlas testAtlas() { return this.testAtlas;