source: OpenWorkouts-current/ow/tests/test_utilities.py @ 8c2b094

current
Last change on this file since 8c2b094 was d6da99e, checked in by Borja Lopez <borja@…>, 5 years ago

Fixed pep8/pycodestyle

  • Property mode set to 100644
File size: 12.0 KB
Line 
1import os
2from datetime import timedelta, datetime
3from unittest.mock import patch, Mock
4from pyexpat import ExpatError
5from xml.dom.minidom import Element
6
7import pytest
8from pyramid.testing import DummyRequest
9
10from ow.models.root import OpenWorkouts
11from ow.models.user import User
12from ow.models.workout import Workout
13
14from ow.utilities import (
15    slugify,
16    GPXMinidomParser,
17    semicircles_to_degrees,
18    degrees_to_semicircles,
19    miles_to_kms,
20    kms_to_miles,
21    meters_to_kms,
22    kms_to_meters,
23    mps_to_kmph,
24    kmph_to_mps,
25    save_map_screenshot,
26    timedelta_to_hms,
27    get_week_days,
28    get_month_week_number,
29    part_of_day
30)
31
32from ow.tests.helpers import join
33
34
35class TestUtilities(object):
36
37    @pytest.fixture
38    def john(self):
39        john = User(firstname='John', lastname='Doe',
40                    email='john.doe@example.net')
41        john.password = 's3cr3t'
42        return john
43
44    @pytest.fixture
45    def root(self, john):
46        root = OpenWorkouts()
47        root.add_user(john)
48        john['1'] = Workout(
49            duration=timedelta(minutes=60),
50            distance=30
51        )
52        return root
53
54    def test_slugify(self):
55        res = slugify(u'long story SHORT      ')
56        assert res == u'long-story-short'
57        res = slugify(u'bla \u03ba\u03b1\u03bb\u03ac \u03c0\u03b1\u03c2')
58        assert res == u'bla-kala-pas'
59
60    def test_slugify_special_chars(self):
61        res = slugify(u'(r)-[i]\u00AE')
62        assert res == u'r-i-r'
63
64    def test_semicircles_to_degrees(self):
65        assert semicircles_to_degrees(10) == 10 * (180 / pow(2, 31))
66
67    def test_degrees_to_semicircles(self):
68        assert degrees_to_semicircles(10) == 10 * (pow(2, 31) / 180)
69
70    def test_miles_to_kms(self):
71        assert miles_to_kms(100) == 100 / 0.62137119
72
73    def test_kms_to_miles(self):
74        assert kms_to_miles(100) == 100 * 0.62137119
75
76    def test_meters_to_kms(self):
77        assert meters_to_kms(1000) == 1
78
79    def test_kms_to_meters(self):
80        assert kms_to_meters(1) == 1000
81
82    def test_mps_to_kmph(self):
83        assert mps_to_kmph(5) == 5 * 3.6
84
85    def test_kmph_to_mps(self):
86        assert kmph_to_mps(30) == 30 * 0.277778
87
88    @patch('ow.utilities.shutil')
89    @patch('ow.utilities.os')
90    @patch('ow.utilities.Browser')
91    def test_save_map_screenshot_no_gpx(
92            self, Browser, os, shutil, root, john):
93        request = DummyRequest()
94        saved = save_map_screenshot(john['1'], request)
95        assert not saved
96        assert not Browser.called
97        assert not os.path.abspath.called
98        assert not os.path.dirname.called
99        assert not os.path.join.called
100        assert not os.path.exists.called
101        assert not os.makedirs.called
102        assert not shutil.move.called
103        # even having a fit tracking file, nothing is done
104        john['1'].tracking_file = 'faked fit file'
105        john['1'].tracking_filetype = 'fit'
106        saved = save_map_screenshot(john['1'], request)
107        assert not saved
108        assert not Browser.called
109        assert not os.path.abspath.called
110        assert not os.path.dirname.called
111        assert not os.path.join.called
112        assert not os.path.exists.called
113        assert not os.makedirs.called
114        assert not shutil.move.called
115
116    @patch('ow.utilities.shutil')
117    @patch('ow.utilities.os')
118    @patch('ow.utilities.Browser')
119    def test_save_map_screenshot_with_gpx(
120            self, Browser, os, shutil, root, john):
121        request = DummyRequest()
122        browser = Mock()
123        Browser.return_value = browser
124        os.path.abspath.return_value = 'current_dir'
125        os.path.join.side_effect = join
126        # This mimics what happens when the directory for this user map
127        # screenshots does not exist, which means we don'have to create one
128        # (calling os.makedirs)
129        os.path.exists.return_value = False
130
131        map_url = request.resource_url(john['1'], 'map')
132
133        john['1'].tracking_file = 'faked gpx content'
134        john['1'].tracking_filetype = 'gpx'
135        saved = save_map_screenshot(john['1'], request)
136        assert saved
137        Browser.assert_called_once_with('chrome', headless=True)
138        browser.driver.set_window_size.assert_called_once_with(1300, 436)
139        browser.visit.assert_called_once_with(map_url)
140        browser.screenshot.assert_called_once
141        os.path.abspath.assert_called_once
142        assert os.path.dirname.called
143        assert os.path.join.call_count == 2
144        assert os.path.exists.called
145        assert os.makedirs.called
146        os.shutil.move.assert_called_once
147
148    @patch('ow.utilities.shutil')
149    @patch('ow.utilities.os')
150    @patch('ow.utilities.Browser')
151    def test_save_map_screenshot_with_gpx_makedirs(
152            self, Browser, os, shutil, root, john):
153        request = DummyRequest()
154        browser = Mock()
155        Browser.return_value = browser
156        os.path.abspath.return_value = 'current_dir'
157        os.path.join.side_effect = join
158        # If os.path.exists returns True, makedirs is not called
159        os.path.exists.return_value = True
160
161        map_url = request.resource_url(john['1'], 'map')
162
163        john['1'].tracking_file = 'faked gpx content'
164        john['1'].tracking_filetype = 'gpx'
165        saved = save_map_screenshot(john['1'], request)
166        assert saved
167        Browser.assert_called_once_with('chrome', headless=True)
168        browser.driver.set_window_size.assert_called_once_with(1300, 436)
169        browser.visit.assert_called_once_with(map_url)
170        browser.screenshot.assert_called_once
171        os.path.abspath.assert_called_once
172        assert os.path.dirname.called
173        assert os.path.join.call_count == 2
174        assert os.path.exists.called
175        assert not os.makedirs.called
176        os.shutil.move.assert_called_once
177
178    def test_timedelta_to_hms(self):
179        value = timedelta(seconds=0)
180        assert timedelta_to_hms(value) == (0, 0, 0)
181        value = timedelta(seconds=3600)
182        assert timedelta_to_hms(value) == (1, 0, 0)
183        value = timedelta(seconds=3900)
184        assert timedelta_to_hms(value) == (1, 5, 0)
185        value = timedelta(seconds=3940)
186        assert timedelta_to_hms(value) == (1, 5, 40)
187        value = timedelta(seconds=4)
188        assert timedelta_to_hms(value) == (0, 0, 4)
189        value = timedelta(seconds=150)
190        assert timedelta_to_hms(value) == (0, 2, 30)
191        # try now something that is not a timedelta
192        with pytest.raises(AttributeError):
193            timedelta_to_hms('not a timedelta')
194
195    def test_week_days(self):
196        # get days from a monday, week starting on monday
197        days = get_week_days(datetime(2019, 1, 21))
198        assert len(days) == 7
199        matches = [
200            [days[0], datetime(2019, 1, 21)],
201            [days[1], datetime(2019, 1, 22)],
202            [days[2], datetime(2019, 1, 23)],
203            [days[3], datetime(2019, 1, 24)],
204            [days[4], datetime(2019, 1, 25)],
205            [days[5], datetime(2019, 1, 26)],
206            [days[6], datetime(2019, 1, 27)]
207        ]
208        for m in matches:
209            assert m[0] == m[1]
210        # get days from a wednesday, week starting on monday
211        days = get_week_days(datetime(2019, 1, 23))
212        assert len(days) == 7
213        matches = [
214            [days[0], datetime(2019, 1, 21)],
215            [days[1], datetime(2019, 1, 22)],
216            [days[2], datetime(2019, 1, 23)],
217            [days[3], datetime(2019, 1, 24)],
218            [days[4], datetime(2019, 1, 25)],
219            [days[5], datetime(2019, 1, 26)],
220            [days[6], datetime(2019, 1, 27)]
221        ]
222        for m in matches:
223            assert m[0] == m[1]
224        # get days from a monday, but week starting on sunday now
225        days = get_week_days(datetime(2019, 1, 21), start_day=0)
226        assert len(days) == 7
227        matches = [
228            [days[0], datetime(2019, 1, 20)],
229            [days[1], datetime(2019, 1, 21)],
230            [days[2], datetime(2019, 1, 22)],
231            [days[3], datetime(2019, 1, 23)],
232            [days[4], datetime(2019, 1, 24)],
233            [days[5], datetime(2019, 1, 25)],
234            [days[6], datetime(2019, 1, 26)]
235        ]
236        for m in matches:
237            assert m[0] == m[1]
238
239    def test_get_month_week_number(self):
240        # first day of january 2019, this is week number 1
241        day = datetime(2019, 1, 1)
242        week_number = get_month_week_number(day)
243        assert week_number == 0
244        # add 7 days (1 more week), week number is 2
245        day += timedelta(days=7)
246        week_number = get_month_week_number(day)
247        assert week_number == 1
248        # add days enough to move to the next month, we restart
249        # week number counting
250        day += timedelta(days=25)
251        week_number = get_month_week_number(day)
252        assert week_number == 0
253
254    def test_part_of_day(self):
255        parts = [
256            ((5, 11), 'Morning'),
257            ((12, 17), 'Afternoon'),
258            ((18, 22), 'Evening'),
259            ((23, 4), 'Night'),
260        ]
261        for part in parts:
262            hours_range = range(*part[0])
263            expected = part[1]
264            for hour in hours_range:
265                dt = datetime(2019, 1, 15, hour, 0)
266                assert part_of_day(dt) == expected
267
268
269class TestGPXParseMinidom(object):
270
271    def gpx_file(self, filename):
272        """
273        Return the full path to the given filename from the available fixtures
274        """
275        here = os.path.abspath(os.path.dirname(__file__))
276        path = os.path.join(here, 'fixtures', filename)
277        return path
278
279    def test_load_gpx_invalid(self):
280        gpx_file = self.gpx_file('invalid.gpx')
281        parser = GPXMinidomParser(gpx_file)
282        with pytest.raises(ExpatError):
283            parser.load_gpx()
284        assert parser.gpx is None
285
286    gpx_files = [
287        ('empty.gpx', Element),
288        ('20131013.gpx', Element),  # GPX 1.0 file
289        ('20160129-with-extensions.gpx', Element),  # GPX 1.1 file with ext.
290    ]
291
292    @pytest.mark.parametrize(('filename', 'expected'), gpx_files)
293    def test_load_gpx(self, filename, expected):
294        """
295        Loading valid gpx files ends in the gpx attribute of the parser
296        being a xml.dom.minidom.Element object, not matter if the gpx file
297        is empty or a 1.0/1.1 gpx file.
298        """
299        gpx_file = self.gpx_file(filename)
300        parser = GPXMinidomParser(gpx_file)
301        parser.load_gpx()
302        assert isinstance(parser.gpx, expected)
303
304    def test_parse_tracks_empty_gpx(self):
305        gpx_file = self.gpx_file('empty.gpx')
306        parser = GPXMinidomParser(gpx_file)
307        parser.load_gpx()
308        parser.parse_tracks()
309        assert parser.tracks == {}
310
311    def test_parse_tracks_1_0_gpx(self):
312        """
313        Parsing a GPX 1.0 file with no extensions. The points in the track
314        contain keys for the well-known extensions (hr, cad, atemp), but their
315        values are None
316        """
317        gpx_file = self.gpx_file('20131013.gpx')
318        parser = GPXMinidomParser(gpx_file)
319        parser.load_gpx()
320        parser.parse_tracks()
321        keys = list(parser.tracks.keys())
322        assert keys == [u'A ride I will never forget']
323        point = parser.tracks[keys[0]][0]
324        data = ['lat', 'lon', 'ele', 'time']
325        for d in data:
326            assert point[d] is not None
327        extensions = ['hr', 'cad', 'atemp']
328        for e in extensions:
329            assert point[e] is None
330
331    def test_parse_tracks_1_1_gpx(self):
332        """
333        Parsing a GPX 1.1 file with extensions. The points in the track contain
334        keys for the well-known extensions (hr, cad, atemp), with the values
335        taken from the gpx file (although we test only that they are not None)
336        """
337        gpx_file = self.gpx_file('20160129-with-extensions.gpx')
338        parser = GPXMinidomParser(gpx_file)
339        parser.load_gpx()
340        parser.parse_tracks()
341        keys = list(parser.tracks.keys())
342        assert keys == [
343            u'Cota counterclockwise + end bonus']
344        point = parser.tracks[keys[0]][0]
345        data = ['lat', 'lon', 'ele', 'time']
346        for d in data:
347            assert point[d] is not None
348        extensions = ['hr', 'cad', 'atemp']
349        for e in extensions:
350            assert point[e] is not None
Note: See TracBrowser for help on using the repository browser.