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

currentfeature/docs
Last change on this file since d517001 was d517001, checked in by Borja Lopez <borja@…>, 5 years ago

(#58) Set a title automatically when adding manually a workout without
providing one.

The title is generated based on the only required data we have (starting
date and time) + sport (if provided).

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