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
RevLine 
[5ec3a0b]1import os
[2f8a48f]2from datetime import timedelta, datetime
[d6f8304]3from unittest.mock import patch, Mock
[5ec3a0b]4from pyexpat import ExpatError
5from xml.dom.minidom import Element
6
7import pytest
[d6f8304]8from pyramid.testing import DummyRequest
[5ec3a0b]9
[ceae158]10from ow.models.root import OpenWorkouts
11from ow.models.user import User
12from ow.models.workout import Workout
13
[b73ae09]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,
[2f8a48f]25    save_map_screenshot,
26    timedelta_to_hms,
[d517001]27    get_week_days,
[79f154d]28    get_month_week_number,
[d517001]29    part_of_day
[b73ae09]30)
[5ec3a0b]31
[ceae158]32from ow.tests.helpers import join
33
[5ec3a0b]34
35class TestUtilities(object):
36
[ceae158]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
[5ec3a0b]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
[b73ae09]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
[d6f8304]88    @patch('ow.utilities.shutil')
[ceae158]89    @patch('ow.utilities.os')
[d6f8304]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)
[ceae158]95        assert not saved
[d6f8304]96        assert not Browser.called
[ceae158]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
[d6f8304]102        assert not shutil.move.called
[ceae158]103        # even having a fit tracking file, nothing is done
104        john['1'].tracking_file = 'faked fit file'
105        john['1'].tracking_filetype = 'fit'
[d6f8304]106        saved = save_map_screenshot(john['1'], request)
[ceae158]107        assert not saved
[d6f8304]108        assert not Browser.called
[ceae158]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
[d6f8304]114        assert not shutil.move.called
[ceae158]115
[d6f8304]116    @patch('ow.utilities.shutil')
[ceae158]117    @patch('ow.utilities.os')
[d6f8304]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
[ceae158]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
[d6f8304]131        map_url = request.resource_url(john['1'], 'map')
132
[ceae158]133        john['1'].tracking_file = 'faked gpx content'
134        john['1'].tracking_filetype = 'gpx'
[d6f8304]135        saved = save_map_screenshot(john['1'], request)
[ceae158]136        assert saved
[d6f8304]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
[ceae158]141        os.path.abspath.assert_called_once
142        assert os.path.dirname.called
[d6f8304]143        assert os.path.join.call_count == 2
[ceae158]144        assert os.path.exists.called
145        assert os.makedirs.called
[d6f8304]146        os.shutil.move.assert_called_once
[ceae158]147
[d6f8304]148    @patch('ow.utilities.shutil')
[ceae158]149    @patch('ow.utilities.os')
[d6f8304]150    @patch('ow.utilities.Browser')
[ceae158]151    def test_save_map_screenshot_with_gpx_makedirs(
[d6f8304]152            self, Browser, os, shutil, root, john):
153        request = DummyRequest()
154        browser = Mock()
155        Browser.return_value = browser
[ceae158]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
[d6f8304]161        map_url = request.resource_url(john['1'], 'map')
162
[ceae158]163        john['1'].tracking_file = 'faked gpx content'
164        john['1'].tracking_filetype = 'gpx'
[d6f8304]165        saved = save_map_screenshot(john['1'], request)
[ceae158]166        assert saved
[d6f8304]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
[ceae158]171        os.path.abspath.assert_called_once
172        assert os.path.dirname.called
[d6f8304]173        assert os.path.join.call_count == 2
[ceae158]174        assert os.path.exists.called
175        assert not os.makedirs.called
[d6f8304]176        os.shutil.move.assert_called_once
[ceae158]177
[2f8a48f]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
[79f154d]239    def test_get_month_week_number(self):
240        # first day of january 2019, this is week number 1
[d6da99e]241        day = datetime(2019, 1, 1)
[79f154d]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
[d517001]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
[5ec3a0b]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.