|
| 1 | +import typing |
| 2 | +from unittest.mock import MagicMock, patch |
| 3 | + |
| 4 | +import pytest |
| 5 | + |
| 6 | +from main.config import Config |
| 7 | +from main.tests import TestCase |
| 8 | +from project_types.validate.api_calls import ( |
| 9 | + ValidateApiCallError, |
| 10 | + ohsome, |
| 11 | + query_osm, |
| 12 | + query_osmcha, |
| 13 | + remove_noise_and_add_user_info, |
| 14 | + remove_troublesome_chars, |
| 15 | +) |
| 16 | + |
| 17 | + |
| 18 | +class TestValidateProject(TestCase): |
| 19 | + @typing.override |
| 20 | + @classmethod |
| 21 | + def setUpClass(cls): |
| 22 | + super().setUpClass() |
| 23 | + |
| 24 | + def test_remove_troublesome_chars(self): |
| 25 | + data = [ |
| 26 | + ("Hello\nWorld", "Hello World"), |
| 27 | + ('She said "Hello"', "She said Hello"), |
| 28 | + ("It's fine", "Its fine"), |
| 29 | + ("Hello 'World'", "Hello World"), |
| 30 | + (None, None), |
| 31 | + (123, 123), |
| 32 | + ] |
| 33 | + for input_str, expected in data: |
| 34 | + result = remove_troublesome_chars(input_str) |
| 35 | + assert result == expected |
| 36 | + |
| 37 | + @patch("project_types.validate.api_calls.retry_get") |
| 38 | + def test_query_osmcha(self, mock_retry_get): |
| 39 | + changeset_ids = [12345, 67890] |
| 40 | + changeset_results = {} |
| 41 | + |
| 42 | + # Fake API JSON response |
| 43 | + mock_response_data = { |
| 44 | + "features": [ |
| 45 | + { |
| 46 | + "id": "12345", |
| 47 | + "properties": { |
| 48 | + "user": "Sita Devi", |
| 49 | + "uid": 1001, |
| 50 | + "comment": "Looks good!", |
| 51 | + "editor": "Ram", |
| 52 | + }, |
| 53 | + }, |
| 54 | + { |
| 55 | + "id": "67890", |
| 56 | + "properties": { |
| 57 | + "user": "Hari", |
| 58 | + "uid": 1002, |
| 59 | + "comment": "It's not a bridge", |
| 60 | + "editor": "Shyam 'Bahadur'", |
| 61 | + }, |
| 62 | + }, |
| 63 | + ], |
| 64 | + } |
| 65 | + |
| 66 | + mock_response = MagicMock() |
| 67 | + mock_response.status_code = 200 |
| 68 | + mock_response.json.return_value = mock_response_data |
| 69 | + mock_retry_get.return_value = mock_response |
| 70 | + |
| 71 | + query_osmcha(changeset_ids, changeset_results) |
| 72 | + |
| 73 | + assert changeset_results == { |
| 74 | + 12345: { |
| 75 | + "username": "Sita Devi", |
| 76 | + "userid": 1001, |
| 77 | + "comment": "Looks good!", |
| 78 | + "editor": "Ram", |
| 79 | + }, |
| 80 | + 67890: { |
| 81 | + "username": "Hari", |
| 82 | + "userid": 1002, |
| 83 | + "comment": "Its not a bridge", |
| 84 | + "editor": "Shyam Bahadur", |
| 85 | + }, |
| 86 | + } |
| 87 | + |
| 88 | + # Check request is made for osmcha |
| 89 | + mock_retry_get.assert_called_once() |
| 90 | + called_url = mock_retry_get.call_args[0][0] |
| 91 | + assert called_url.startswith(Config.OSMCHA_API_LINK) |
| 92 | + assert "changesets/?ids=12345,67890" in called_url |
| 93 | + |
| 94 | + # check for other status code to raise error |
| 95 | + mock_response.status_code = 403 |
| 96 | + with pytest.raises(ValidateApiCallError): |
| 97 | + query_osmcha(changeset_ids, changeset_results) |
| 98 | + |
| 99 | + @patch("project_types.validate.api_calls.retry_get") |
| 100 | + def test_query_osm(self, mock_retry_get): |
| 101 | + changeset_ids = [12345, 67890] |
| 102 | + changeset_results = {} |
| 103 | + |
| 104 | + xml_response = """ |
| 105 | + <osm> |
| 106 | + <changeset id="12345" user='Sita "Devi"' uid="1001"> |
| 107 | + <tag k="comment" v="Looks good!"/> |
| 108 | + <tag k="created_by" v="Ram"/> |
| 109 | + </changeset> |
| 110 | + <changeset id="67890" user="Hari" uid="1002"> |
| 111 | + <tag k="comment" v="It's not a bridge"/> |
| 112 | + <tag k="created_by" v="Shyam Bahadur"/> |
| 113 | + </changeset> |
| 114 | + </osm> |
| 115 | + """ |
| 116 | + |
| 117 | + mock_response = MagicMock() |
| 118 | + mock_response.status_code = 200 |
| 119 | + mock_response.content = xml_response.encode("utf-8") |
| 120 | + mock_retry_get.return_value = mock_response |
| 121 | + |
| 122 | + result = query_osm(changeset_ids, changeset_results) |
| 123 | + |
| 124 | + assert result == { |
| 125 | + 12345: { |
| 126 | + "username": "Sita Devi", |
| 127 | + "userid": "1001", |
| 128 | + "comment": "Looks good!", |
| 129 | + "editor": "Ram", |
| 130 | + }, |
| 131 | + 67890: { |
| 132 | + "username": "Hari", |
| 133 | + "userid": "1002", |
| 134 | + "comment": "Its not a bridge", |
| 135 | + "editor": "Shyam Bahadur", |
| 136 | + }, |
| 137 | + } |
| 138 | + |
| 139 | + # check for other status code to raise error |
| 140 | + mock_response.status_code = 500 |
| 141 | + with pytest.raises(ValidateApiCallError): |
| 142 | + query_osm([12345], {}) |
| 143 | + |
| 144 | + @patch("requests.post") |
| 145 | + @patch("project_types.validate.api_calls.remove_noise_and_add_user_info") |
| 146 | + def test_ohsome(self, mock_remove_noise, mock_post): |
| 147 | + sample_request = { |
| 148 | + "endpoint": "elements/geometry", |
| 149 | + "filter": "highway=primary", |
| 150 | + } |
| 151 | + sample_area = "POLYGON((8.67 49.39,8.68 49.39,8.68 49.40,8.67 49.40,8.67 49.39))" |
| 152 | + sample_properties = "tags,timestamp" |
| 153 | + |
| 154 | + mock_response_data = { |
| 155 | + "attribution": { |
| 156 | + "url": "https://ohsome.org/copyrights", |
| 157 | + "text": "© OpenStreetMap contributors", |
| 158 | + }, |
| 159 | + "apiVersion": "1.10.4", |
| 160 | + "type": "FeatureCollection", |
| 161 | + "features": [ |
| 162 | + { |
| 163 | + "type": "Feature", |
| 164 | + "geometry": { |
| 165 | + "type": "Point", |
| 166 | + "coordinates": [ |
| 167 | + 8.6861, |
| 168 | + 49.4051089, |
| 169 | + ], |
| 170 | + }, |
| 171 | + "properties": { |
| 172 | + "@osmId": "node/385941986", |
| 173 | + "@snapshotTimestamp": "2019-09-01T00: 00: 00Z", |
| 174 | + }, |
| 175 | + }, |
| 176 | + { |
| 177 | + "type": "Feature", |
| 178 | + "geometry": { |
| 179 | + "type": "Point", |
| 180 | + "coordinates": [ |
| 181 | + 8.6819524, |
| 182 | + 49.3825748, |
| 183 | + ], |
| 184 | + }, |
| 185 | + "properties": { |
| 186 | + "@osmId": "node/699583613", |
| 187 | + "@snapshotTimestamp": "2019-09-01T00: 00: 00Z", |
| 188 | + }, |
| 189 | + }, |
| 190 | + ], |
| 191 | + } |
| 192 | + |
| 193 | + mock_response = MagicMock() |
| 194 | + mock_response.status_code = 200 |
| 195 | + mock_response.json.return_value = mock_response_data |
| 196 | + mock_post.return_value = mock_response |
| 197 | + |
| 198 | + processed_data = {"processed": True, "data": mock_response_data} |
| 199 | + mock_remove_noise.return_value = processed_data |
| 200 | + |
| 201 | + result = ohsome(sample_request, sample_area, sample_properties) |
| 202 | + |
| 203 | + mock_post.assert_called_once() |
| 204 | + mock_remove_noise.assert_called_once_with(mock_response_data) |
| 205 | + assert result == processed_data |
| 206 | + |
| 207 | + # test for error |
| 208 | + status_codes = [400, 401, 403, 404, 500] |
| 209 | + for status_code in status_codes: |
| 210 | + mock_response.status_code = status_code |
| 211 | + with pytest.raises(ValidateApiCallError): |
| 212 | + result = ohsome(sample_request, sample_area, sample_properties) |
| 213 | + |
| 214 | + @patch("project_types.validate.api_calls.query_osmcha") |
| 215 | + @patch("project_types.validate.api_calls.query_osm") |
| 216 | + def test_remove_noise_and_add_user_info(self, mock_query_osm, mock_query_osmcha): |
| 217 | + input_data = { |
| 218 | + "type": "FeatureCollection", |
| 219 | + "features": [ |
| 220 | + { |
| 221 | + "type": "Feature", |
| 222 | + "geometry": {"type": "Point", "coordinates": [0, 0]}, |
| 223 | + "properties": { |
| 224 | + "@changesetId": 12345, |
| 225 | + "@lastEdit": 1234567890, |
| 226 | + "@osmId": 111, |
| 227 | + "@version": 1, |
| 228 | + "unwanted_field": "should_be_removed", |
| 229 | + "another_noise": 999, |
| 230 | + }, |
| 231 | + }, |
| 232 | + { |
| 233 | + "type": "Feature", |
| 234 | + "geometry": {"type": "Point", "coordinates": [1, 1]}, |
| 235 | + "properties": { |
| 236 | + "@changesetId": 12346, |
| 237 | + "@lastEdit": 1234567891, |
| 238 | + "@osmId": 222, |
| 239 | + "@version": 2, |
| 240 | + "extra_data": "also_removed", |
| 241 | + }, |
| 242 | + }, |
| 243 | + ], |
| 244 | + } |
| 245 | + mock_osmcha_response = { |
| 246 | + 12345: { |
| 247 | + "username": "Sita", |
| 248 | + "comment": "This is a test comment", |
| 249 | + "editor": "iD", |
| 250 | + "userid": 1001, |
| 251 | + }, |
| 252 | + 12346: None, |
| 253 | + } |
| 254 | + |
| 255 | + mock_query_osmcha.return_value = mock_osmcha_response |
| 256 | + |
| 257 | + mock_query_osm.return_value = { |
| 258 | + 12345: { |
| 259 | + "username": "Sita", |
| 260 | + "userid": "1001", |
| 261 | + "comment": "This is an updated test comment", |
| 262 | + "editor": "Ram", |
| 263 | + }, |
| 264 | + 12346: { |
| 265 | + "username": "Kiran", |
| 266 | + "userid": "1002", |
| 267 | + "comment": "This is a test comment", |
| 268 | + "editor": "Hari", |
| 269 | + }, |
| 270 | + } |
| 271 | + |
| 272 | + remove_noise_and_add_user_info(input_data) |
| 273 | + mock_query_osmcha.assert_called() |
| 274 | + called_ids = mock_query_osmcha.call_args[0][0] |
| 275 | + assert set(called_ids) == {12345, 12346} |
| 276 | + mock_query_osm.assert_called() |
0 commit comments