diff --git a/rowers/imports.py b/rowers/imports.py index cb1cb83f..a5b5f171 100644 --- a/rowers/imports.py +++ b/rowers/imports.py @@ -93,6 +93,7 @@ def imports_open(user,oauth_data): except (TypeError,AttributeError,KeyError): tokenexpirydate = None + if (token == '') or (token is None): s = "Token doesn't exist. Need to authorize" raise NoTokenError("User has no token") diff --git a/rowers/rp3stuff.py b/rowers/rp3stuff.py index a4f9e99e..2377b2e3 100644 --- a/rowers/rp3stuff.py +++ b/rowers/rp3stuff.py @@ -114,12 +114,14 @@ def get_rp3_workout_list(user): executed_at } }""" + response = requests.post( url=graphql_url, headers=headers, json={'query': get_workouts_list} ) + return response def get_rp3_workouts(rower,do_async=True): diff --git a/rowers/tasks.py b/rowers/tasks.py index c9355451..6b19f50d 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -2857,9 +2857,15 @@ def handle_rp3_async_workout(userid,rp3token,rp3id,startdatetime,max_attempts,de res = requests.get(download_url,headers=headers) + if not startdatetime: startdatetime = str(timezone.now()) + try: + startdatetime = str(startdatetime) + except: + pass + if res.status_code != 200: return 0 diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py index 845690a9..70969d21 100644 --- a/rowers/tests/mocks.py +++ b/rowers/tests/mocks.py @@ -98,7 +98,6 @@ def mocked_myqueue(*args, **kwargs): return Job() def mock_c2open(*args, **kwargs): - print('mock') return('aap') def mocked_session(*args, **kwargs): @@ -123,6 +122,16 @@ def mocked_session(*args, **kwargs): def json(self): return self.json_data + class MockContentResponse: + def __init__(self,filename,status_code): + with open(filename,'rb') as f: + s = f.read() + self.content = s + self.status_code = status_code + self.ok = True + + + return MockEngine() def mocked_sqlalchemy(*args, **kwargs): @@ -697,6 +706,11 @@ def mocked_requests(*args, **kwargs): with open('rowers/tests/testdata/nk_list.json','r') as infile: nkworkoutlist = json.load(infile) + with open('rowers/tests/testdata/rp3_list.json','r') as infile: + rp3workoutlist = json.load(infile) + + rp3linkready = {'data': {'download': {'id': 591621, 'status': 'ready', 'link': 'https://rp3rowing-app.com/api/workouts/591621/download?type=csv'}}} + with open('rowers/tests/testdata/example-session-strokes-with-impeller-data.json','r') as infile: nkimpellerstrokedata = json.load(infile) @@ -781,6 +795,15 @@ def mocked_requests(*args, **kwargs): def json(self): return self.json_data + class MockContentResponse: + def __init__(self,filename,status_code): + with open(filename,'rb') as f: + s = f.read() + self.content = s + self.status_code = status_code + self.ok = True + + class MockHeaderResponse: def __init__(self, header_data, status_code): @@ -832,6 +855,9 @@ def mocked_requests(*args, **kwargs): if 'garmin' in args: return MockOAuth1Session() + if 'url' in kwargs: + if 'rp3' in kwargs['url']: + args = [kwargs['url']] if not args: return MockSession() @@ -845,6 +871,7 @@ def mocked_requests(*args, **kwargs): uatester = re.compile('.*?mapmyfitness\.com') tptester = re.compile('.*?trainingpeaks\.com') nktester = re.compile('.*?nkrowlink\.com') + rp3tester = re.compile('.*?rp3rowing-app\.com') garmintester = re.compile('.*?garmin\.com') c2importregex = '.*?concept2.com\/api\/users\/me\/results\/\d+' @@ -865,6 +892,8 @@ def mocked_requests(*args, **kwargs): nkstrokesregex = '.*?nkrowlink\.com\/api\/v1\/sessions\/strokes' nkstrokestester = re.compile(nkstrokesregex) + rp3graphregex = '.*?rp3rowing-app\.com\/graphql' + rp3graphtester = re.compile(rp3graphregex) stravaathleteregex = '.*?strava\.com\/api\/v3\/athlete$' stravaathletetester = re.compile(stravaathleteregex) @@ -1052,6 +1081,33 @@ def mocked_requests(*args, **kwargs): return MockResponse(nkimpellerworkoutlist,200) return MockResponse(nkworkoutlist,200) + if rp3tester.match(args[0]): + if 'token' in args[0]: + json_data = { + 'access_token': 'TA3n1vrNjuQJWw0TdCDHnjSmrjIPULhTlejMIWqq', + 'expires_in': 604800, + 'refresh_token': 'jHJhFzCfOOKB8oyiayubhLAlxaMkG3ruC1E8YxaR' + } + return MockResponse(json_data,200) + + if 'api' in args[0]: + if '591621' in args[0]: + return MockContentResponse('rowers/tests/testdata/rp3content.csv',200) + + + if rp3graphtester.match(args[0]): + if 'json' in kwargs: + try: + query = kwargs['json']['query'] + if 'download' in query: + return MockResponse(rp3linkready,200) + except KeyError: + pass + json_data = rp3workoutlist + return MockResponse(json_data,200) + + + if stravatester.match(args[0]): diff --git a/rowers/tests/test_imports.py b/rowers/tests/test_imports.py index 6a42f1a4..affe1025 100644 --- a/rowers/tests/test_imports.py +++ b/rowers/tests/test_imports.py @@ -509,6 +509,93 @@ class NKObjects(DjangoTestCase): w = Workout.objects.get(id=1) #self.assertTrue(w.impeller) +#@pytest.mark.django_db +@override_settings(TESTING=True) +class RP3Objects(DjangoTestCase): + def setUp(self): + self.c = Client() + self.u = User.objects.create_user('john', + 'sander@ds.ds', + 'koeinsloot') + + self.u.first_name = 'John' + self.u.last_name = 'Sander' + self.u.save() + self.r = Rower.objects.create(user=self.u,gdproptin=True,surveydone=True, + gdproptindate=timezone.now() + ) + + self.r.rp3token = '12' + self.r.rp3refreshtoken = '123' + self.r.rp3tokenexpirydate = arrow.get(datetime.datetime.now()-datetime.timedelta(days=1)).datetime + + self.r.save() + self.c.login(username='john',password='koeinsloot') + self.nu = datetime.datetime.now() + + filename = 'rowers/tests/testdata/testdata.csv' + + rr = rrower(hrmax=self.r.max,hrut2=self.r.ut2, + hrut1=self.r.ut1,hrat=self.r.at, + hrtr=self.r.tr,hran=self.r.an,ftp=self.r.ftp) + row = rdata(csvfile=filename,rower=rr) + totaldist = row.df['cum_dist'].max() + totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min() + totaltime = totaltime+row.df.loc[:,' ElapsedTime (sec)'].iloc[0] + + + hours = int(totaltime/3600.) + minutes = int((totaltime - 3600.*hours)/60.) + seconds = int(totaltime - 3600.*hours - 60.*minutes) + tenths = int(10*(totaltime - 3600.*hours - 60.*minutes - seconds)) + + duration = "%s:%s:%s.%s" % (hours,minutes,seconds,tenths) + + + workoutdate = row.rowdatetime.strftime('%Y-%m-%d') + workoutstarttime = row.rowdatetime.strftime('%H:%M:%S') + + self.w = Workout.objects.create( + name='testworkout',workouttype='water', + user=self.r,date=self.nu.strftime('%Y-%m-%d'), + starttime=workoutstarttime, + startdatetime=row.rowdatetime, + duration=duration,distance=totaldist, + csvfilename=filename + ) + + @patch('rowers.rp3stuff.requests.get', side_effect=mocked_requests) + @patch('rowers.rp3stuff.requests.post', side_effect=mocked_requests) + @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + def test_rp3_import(self, mock_get, mockpost, + mocked_getsmallrowdata_db): + + response = self.c.get('/rowers/workout/rp3import/591621',follow=True) + + expected_url = reverse('workout_rp3import_view') + + self.assertRedirects(response, + expected_url=expected_url, + status_code=301,target_status_code=200) + + self.assertEqual(response.status_code, 200) + + w = Workout.objects.get(id=1) + + @patch('rowers.tasks.requests.get',side_effect=mocked_requests) + @patch('rowers.tasks.requests.post',side_effect=mocked_requests) + @patch('rowers.tasks.requests.session', side_effect=mocked_requests) + def test_handle_rp3_get_workouts(self, mock_get, mockpost,MockSession): + userid = self.u.id + rp3token = 'ap' + rp3id = '591621' + startdatetime = timezone.now()-datetime.timedelta(days=30) + max_attempts = 2 + + res = tasks.handle_rp3_async_workout(userid,rp3token,rp3id,startdatetime,max_attempts) + self.assertEqual(res,1) + + #@pytest.mark.django_db @override_settings(TESTING=True) diff --git a/rowers/tests/testdata/rp3content.csv b/rowers/tests/testdata/rp3content.csv new file mode 100644 index 00000000..a761e8d5 --- /dev/null +++ b/rowers/tests/testdata/rp3content.csv @@ -0,0 +1,7 @@ +id,workout_interval_id,ref,stroke_number,power,avg_power,stroke_rate,time,stroke_length,distance,distance_per_stroke,estimated_500m_time,energy_per_stroke,energy_sum,pulse,work_per_pulse,peak_force,peak_force_pos,rel_peak_force_pos,drive_time,recover_time,k,curve_data,stroke_number_in_interval,avg_calculated_power +,963271,55,1,31.73,28.78,65.05,0.95,83.6,2.07,207.2,222.6,537.75,0.54,0,0,509,26.4,31.58,0.92,0,1,"1,181,327,391,410,432,449,464,479,492,503,509,509,507,502,493,482,469,461,452,441,429,416,400,381,364,345,327,312,298,283,260,228,183,135,86,40,2,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768",1,31.73 +,963271,56,2,94.06,53.18,71.18,1.8,70.4,4.79,271.98,154.96,356.01,0.89,0,0,435,26.4,37.5,0.47,0.37,0.82,"29,25,83,167,283,366,401,419,413,420,431,435,425,422,405,391,385,372,366,361,342,326,312,290,275,258,232,203,162,111,68,39,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768",2,62.895 +,963271,57,3,156.86,90.58,77.96,2.43,61.6,7.74,294.47,130.67,262.23,1.16,0,0,364,17.6,28.57,0.36,0.41,0.8,"44,36,113,190,293,338,342,364,328,343,352,338,343,339,336,317,309,288,268,260,249,217,203,164,136,107,76,36,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768",3,94.216666666667 +,963271,58,4,182.59,122.14,79.38,3.06,57.2,10.78,304.24,124.22,208.42,1.36,0,0,318,17.6,30.77,0.32,0.44,0.81,"52,41,116,174,277,312,299,318,302,296,313,292,291,281,271,258,238,233,203,199,167,129,108,76,45,5,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768",4,116.31 +,963271,59,5,185.23,101.92,88.26,4.08,41.8,13.53,274.94,123.63,92.53,1.46,0,0,203,13.2,31.58,0.23,0.45,0.8,"55,21,72,125,170,203,181,174,170,158,151,150,139,109,110,91,69,59,41,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768",5,130.094 +,963271,60,6,180.11,111.58,98.39,4.68,15.4,15.97,244.35,124.79,14.35,1.47,0,0,59,6.6,42.86,0.09,0.52,0.8,"52,30,59,46,48,44,16,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768",6,138.43