Passed
Pull Request — master (#481)
by Jaisen
03:48 queued 17s
created

test_exiftool_geolocation_no_match()   A

Complexity

Conditions 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nop 1
dl 0
loc 10
rs 10
c 0
b 0
f 0
1
from __future__ import absolute_import
2
from __future__ import division
3
from builtins import range
4
from nose.plugins.skip import SkipTest
5
from past.utils import old_div
6
# Project imports
7
import mock
8
import os
9
import random
10
import re
11
import sys
12
from mock import patch
13
from tempfile import gettempdir
14
15
try:
16
    from StringIO import StringIO
17
except ImportError:
18
    from io import StringIO
19
20
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))))
21
22
from . import helper
23
from elodie import geolocation
24
25
os.environ['TZ'] = 'GMT'
26
27
28
def test_decimal_to_dms():
29
30
    for x in range(0, 1000):
31
        target_decimal_value = random.uniform(0.0, 180.0)
32
        if(x % 2 == 1):
33
            target_decimal_value = target_decimal_value * -1
34
35
        dms = geolocation.decimal_to_dms(target_decimal_value)
36
37
        check_value = (dms[0] + dms[1] / 60 + dms[2] / 3600) * dms[3]
38
39
        target_decimal_value = round(target_decimal_value, 8)
40
        check_value = round(check_value, 8)
41
42
        assert target_decimal_value == check_value, '%s does not match %s' % (check_value, target_decimal_value)
43
44
def test_dms_to_decimal_positive_sign():
45
    decimal = geolocation.dms_to_decimal(10, 20, 100, 'NE')
46
    assert helper.isclose(decimal, 10.3611111111)
47
48
    decimal = geolocation.dms_to_decimal(10, 20, 100, 'ne')
49
    assert helper.isclose(decimal, 10.3611111111)
50
51
def test_dms_to_decimal_negative_sign():
52
    decimal = geolocation.dms_to_decimal(10, 20, 100, 'SW')
53
    assert helper.isclose(decimal, -10.3611111111)
54
55
    decimal = geolocation.dms_to_decimal(10, 20, 100, 'sw')
56
    assert helper.isclose(decimal, -10.3611111111)
57
58 View Code Duplication
def test_dms_string_latitude():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
59
60
    for x in range(0, 5):
61
        target_decimal_value = random.uniform(0.0, 180.0)
62
        if(x % 2 == 1):
63
            target_decimal_value = target_decimal_value * -1
64
65
        dms = geolocation.decimal_to_dms(target_decimal_value)
66
        dms_string = geolocation.dms_string(target_decimal_value, 'latitude')
67
68
        check_value = 'N' if target_decimal_value >= 0 else 'S'
69
70
        assert check_value in dms_string, '%s not in %s' % (check_value, dms_string)
71
        assert str(dms[0]) in dms_string, '%s not in %s' % (dms[0], dms_string)
72
73 View Code Duplication
def test_dms_string_longitude():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
74
75
    for x in range(0, 5):
76
        target_decimal_value = random.uniform(0.0, 180.0)
77
        if(x % 2 == 1):
78
            target_decimal_value = target_decimal_value * -1
79
80
        dms = geolocation.decimal_to_dms(target_decimal_value)
81
        dms_string = geolocation.dms_string(target_decimal_value, 'longitude')
82
83
        check_value = 'E' if target_decimal_value >= 0 else 'W'
84
85
        assert check_value in dms_string, '%s not in %s' % (check_value, dms_string)
86
        assert str(dms[0]) in dms_string, '%s not in %s' % (dms[0], dms_string)
87
88
def test_reverse_lookup_with_valid_key():
89
    res = geolocation.lookup(lat=37.368, lon=-122.03)
90
    assert res['address']['city'] == 'Sunnyvale', res
91
92
def test_reverse_lookup_with_invalid_lat_lon():
93
    res = geolocation.lookup(lat=999, lon=999)
94
    assert res is None, res
95
96
@mock.patch('elodie.geolocation.__KEY__', 'invalid_key')
97
def test_reverse_lookup_with_invalid_key():
98
    res = geolocation.lookup(lat=37.368, lon=-122.03)
99
    assert res is None, res
100
101
def test_lookup_with_valid_key():
102
    res = geolocation.lookup(location='Sunnyvale, CA')
103
    latLng = res['results'][0]['locations'][0]['latLng']
104
    assert latLng['lat'] == 37.37188, latLng
105
    assert latLng['lng'] == -122.03751, latLng
106
107
def test_lookup_with_invalid_location():
108
    res = geolocation.lookup(location='foobar dne')
109
    # Mapquest API started returning json (gh-475)
110
    assert res is not None, res
111
112
@mock.patch('elodie.geolocation.__PREFER_ENGLISH_NAMES__', True)
113
def test_lookup_with_prefer_english_names_true():
114
    raise SkipTest("gh-425 MapQuest API no longer supports prefer_english_names.")
115
    res = geolocation.lookup(lat=55.66333, lon=37.61583)
116
    assert res['address']['city'] == 'Nagorny District', res
117
118
@mock.patch('elodie.geolocation.__PREFER_ENGLISH_NAMES__', False)
119
def test_lookup_with_prefer_english_names_false():
120
    raise SkipTest("gh-425 MapQuest API no longer supports prefer_english_names.")
121
    res = geolocation.lookup(lat=55.66333, lon=37.61583)
122
    assert res['address']['city'] == u'\u041d\u0430\u0433\u043e\u0440\u043d\u044b\u0439 \u0440\u0430\u0439\u043e\u043d', res
123
124
@mock.patch('elodie.constants.debug', True)
125
def test_lookup_debug_mapquest_url():
126
    out = StringIO()
127
    sys.stdout = out
128
    res = geolocation.lookup(location='Sunnyvale, CA')
129
    output = out.getvalue()
130
    assert 'MapQuest url:' in output, output
131
132 View Code Duplication
@mock.patch('elodie.constants.location_db', '%s/location.json-cached' % gettempdir())
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
133
def test_place_name_deprecated_string_cached():
134
    # See gh-160 for backwards compatability needed when a string is stored instead of a dict
135
    helper.reset_dbs()
136
    with open('%s/location.json-cached' % gettempdir(), 'w') as f:
137
        f.write("""
138
[{"lat": 37.3667027222222, "long": -122.033383611111, "name": "OLDVALUE"}]
139
"""
140
    )
141
    place_name = geolocation.place_name(37.3667027222222, -122.033383611111)
142
    helper.restore_dbs()
143
144
    assert place_name['city'] == 'Sunnyvale', place_name
145
146 View Code Duplication
@mock.patch('elodie.constants.location_db', '%s/location.json-cached' % gettempdir())
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
147
def test_place_name_cached():
148
    helper.reset_dbs()
149
    with open('%s/location.json-cached' % gettempdir(), 'w') as f:
150
        f.write("""
151
[{"lat": 37.3667027222222, "long": -122.033383611111, "name": {"city": "UNITTEST"}}]
152
"""
153
    )
154
    place_name = geolocation.place_name(37.3667027222222, -122.033383611111)
155
    helper.restore_dbs()
156
157
    assert place_name['city'] == 'UNITTEST', place_name
158
159
def test_place_name_no_default():
160
    # See gh-160 for backwards compatability needed when a string is stored instead of a dict
161
    helper.reset_dbs()
162
    place_name = geolocation.place_name(123456.000, 123456.000)
163
    helper.restore_dbs()
164
165
    assert place_name['default'] == 'Unknown Location', place_name
166
167
@mock.patch('elodie.geolocation.__KEY__', 'invalid_key')
168
def test_lookup_with_invalid_key():
169
    res = geolocation.lookup(location='Sunnyvale, CA')
170
    assert res is None, res
171
172
@mock.patch('elodie.geolocation.__KEY__', '')
173
def test_lookup_with_no_key():
174
    res = geolocation.lookup(location='Sunnyvale, CA')
175
    assert res is None, res
176
177
def test_parse_result_with_error():
178
    res = geolocation.parse_result({'error': 'foo'})
179
    assert res is None, res
180
181
def test_parse_result_with_city():
182
    # https://www.mapquestapi.com/geocoding/v1/reverse?location=37.37188,-122.03751&key=key_goes_here&format=json
183
    results = {"info":{"statuscode":0,"copyright":{"text":"© 2022 MapQuest, Inc.","imageUrl":"http://api.mqcdn.com/res/mqlogo.gif","imageAltText":"© 2022 MapQuest, Inc."},"messages":[]},"options":{"maxResults":1,"ignoreLatLngInput":False},"results":[{"providedLocation":{"latLng":{"lat":37.368,"lng":-122.03}},"locations":[{"street":"312 Old San Francisco Rd","adminArea6":"Heritage District","adminArea6Type":"Neighborhood","adminArea5":"Sunnyvale","adminArea5Type":"City","adminArea4":"Santa Clara","adminArea4Type":"County","adminArea3":"CA","adminArea3Type":"State","adminArea1":"US","adminArea1Type":"Country","postalCode":"94086","geocodeQualityCode":"P1AAA","geocodeQuality":"POINT","dragPoint":False,"sideOfStreet":"R","linkId":"0","unknownInput":"","type":"s","latLng":{"lat":37.36798,"lng":-122.03018},"displayLatLng":{"lat":37.36785,"lng":-122.03021},"mapUrl":""}]}]}
184
185
    res = geolocation.parse_result(results)
186
    assert res == results, res
187
188
def test_parse_result_with_lat_lon():
189
    # https://www.mapquestapi.com/geocoding/v1/address?format=json&key=key_goes_here&locale=en_US&location=Sunnyvale,CA
190
    results = {"info":{"statuscode":0,"copyright":{"text":"© 2022 MapQuest, Inc.","imageUrl":"http://api.mqcdn.com/res/mqlogo.gif","imageAltText":"© 2022 MapQuest, Inc."},"messages":[]},"options":{"maxResults":-1,"ignoreLatLngInput":False},"results":[{"providedLocation":{"location":"Sunnyvale,CA"},"locations":[{"street":"","adminArea6":"","adminArea6Type":"Neighborhood","adminArea5":"Sunnyvale","adminArea5Type":"City","adminArea4":"Santa Clara","adminArea4Type":"County","adminArea3":"CA","adminArea3Type":"State","adminArea1":"US","adminArea1Type":"Country","postalCode":"","geocodeQualityCode":"A5XAX","geocodeQuality":"CITY","dragPoint":False,"sideOfStreet":"N","linkId":"0","unknownInput":"","type":"s","latLng":{"lat":37.37188,"lng":-122.03751},"displayLatLng":{"lat":37.37188,"lng":-122.03751},"mapUrl":""}]}]}
191
192
    res = geolocation.parse_result(results)
193
    assert res == results, res
194
195
def test_parse_result_with_unknown_lat_lon():
196
    # https://www.mapquestapi.com/geocoding/v1/address?format=json&key=key_goes_here&locale=en_US&location=ABCDEFGHIJKLMNOPQRSTUVWXYZ
197
    results = {"info":{"statuscode":0,"copyright":{"text":"© 2022 MapQuest, Inc.","imageUrl":"http://api.mqcdn.com/res/mqlogo.gif","imageAltText":"© 2022 MapQuest, Inc."},"messages":[]},"options":{"maxResults":-1,"ignoreLatLngInput":False},"results":[{"providedLocation":{"location":"ABCDEFGHIJKLMNOPQRSTUVWXYZ"},"locations":[{"street":"","adminArea6":"","adminArea5":"","adminArea4":"","adminArea3":"","adminArea1":"US","postalCode":"","geocodeQualityCode":"A1XAX","geocodeQuality":"COUNTRY","dragPoint":False,"sideOfStreet":"N","linkId":"0","unknownInput":"","type":"s","latLng":{"lat":38.89037,"lng":-77.03196},"displayLatLng":{"lat":38.89037,"lng":-77.03196},"mapUrl":""}]}]}
198
199
    res = geolocation.parse_result(results)
200
    assert res is None, res
201
202
@mock.patch('elodie.geolocation.ExifTool')
203
def test_exiftool_geolocation_success(mock_exiftool_class):
204
    # Mock ExifTool response for Sunnyvale coordinates
205
    mock_et = mock_exiftool_class.return_value.__enter__.return_value
206
    mock_et.execute.return_value = b"Geolocation database:\nCity,Region,Subregion,CountryCode,Country,TimeZone,FeatureCode,Population,Latitude,Longitude\nCupertino,California,Santa Clara County,US,United States,America/Los_Angeles,PPL,60000,37.3229,-122.0322\nSunnyvale,California,Santa Clara County,US,United States,America/Los_Angeles,PPL,152000,37.3688,-122.0363"
207
    
208
    result = geolocation.exiftool_geolocation(37.368, -122.03)
209
    
210
    assert result is not None, "ExifTool geolocation should return result"
211
    assert 'city' in result, "Result should contain city"
212
    assert 'default' in result, "Result should contain default location"
213
    assert result['city'] in ['Cupertino', 'Sunnyvale'], f"City should be Cupertino or Sunnyvale, got {result['city']}"
214
215
@mock.patch('elodie.geolocation.ExifTool')
216
def test_exiftool_geolocation_no_match(mock_exiftool_class):
217
    # Mock ExifTool response with no nearby cities
218
    mock_et = mock_exiftool_class.return_value.__enter__.return_value
219
    mock_et.execute.return_value = b"Geolocation database:\nCity,Region,Subregion,CountryCode,Country,TimeZone,FeatureCode,Population,Latitude,Longitude\nNew York,New York,New York County,US,United States,America/New_York,PPL,8000000,40.7128,-74.0060"
220
    
221
    result = geolocation.exiftool_geolocation(37.368, -122.03)
222
    
223
    # Should return None since New York is too far from Sunnyvale coordinates
224
    assert result is None, "ExifTool geolocation should return None for distant coordinates"
225
226
@mock.patch('elodie.geolocation.ExifTool')
227
def test_exiftool_geolocation_exception(mock_exiftool_class):
228
    # Mock ExifTool to raise an exception
229
    mock_exiftool_class.return_value.__enter__.side_effect = Exception("ExifTool error")
230
    
231
    result = geolocation.exiftool_geolocation(37.368, -122.03)
232
    
233
    assert result is None, "ExifTool geolocation should return None on exception"
234
235
def test_exiftool_geolocation_invalid_coordinates():
236
    result = geolocation.exiftool_geolocation(None, None)
237
    assert result is None, "ExifTool geolocation should return None for None coordinates"
238
    
239
    result = geolocation.exiftool_geolocation(37.368, None)
240
    assert result is None, "ExifTool geolocation should return None for partial coordinates"
241
242
@mock.patch('elodie.geolocation.exiftool_geolocation')
243
@mock.patch('elodie.geolocation.get_key')
244
def test_place_name_uses_mapquest_when_key_available(mock_get_key, mock_exiftool_geo):
245
    # Mock MapQuest key to be available
246
    mock_get_key.return_value = 'test_key'
247
    
248
    # This will test that when MapQuest key is available, it uses MapQuest (not ExifTool)
249
    result = geolocation.place_name(37.368, -122.03)
250
    
251
    # Should have checked for MapQuest key first
252
    mock_get_key.assert_called_once()
253
    # Should NOT have called ExifTool since MapQuest key is available
254
    mock_exiftool_geo.assert_not_called()
255
256
@mock.patch('elodie.geolocation.exiftool_geolocation')
257
@mock.patch('elodie.geolocation.get_key')
258
def test_place_name_uses_exiftool_when_no_mapquest_key(mock_get_key, mock_exiftool_geo):
259
    # Mock no MapQuest key available
260
    mock_get_key.return_value = None
261
    # Mock ExifTool to return a successful result
262
    mock_exiftool_geo.return_value = {'city': 'Cupertino', 'default': 'Cupertino', 'country': 'United States'}
263
    
264
    result = geolocation.place_name(37.368, -122.03)
265
    
266
    # Should have checked for MapQuest key first
267
    mock_get_key.assert_called_once()
268
    # Should have called ExifTool since no MapQuest key
269
    mock_exiftool_geo.assert_called_once_with(37.368, -122.03)
270
    # Should return the ExifTool result
271
    assert result['city'] == 'Cupertino', f"Expected Cupertino, got {result.get('city')}"
272