def test_invalid_bad_auth_code(self): '''検証が正しくない: auth_codeが不正な値が与えられている ''' # 実行・検証 fb = Fitbit(client_id=self.fake_client_id, client_secret=self.fake_client_secret, auth_code=self.fake_auth_code) with pytest.raises(urllib.error.HTTPError, match='HTTP Error 401: Unauthorized'): fb.fetch_tokens()
def test_invalid_bad_refresh_token(self): '''検証が正しくない: refresh_tokenが期限切れもしくは誤っている ''' # 実行・検証 fb = Fitbit(client_id=self.fake_client_id, client_secret=self.fake_client_secret, access_token=self.bad_access_token, refresh_token=self.fake_refresh_token) with pytest.raises(urllib.error.HTTPError, match='HTTP Error 401: Unauthorized'): fb.refresh_access_token()
def test_invalid_redirect_uri_not_string(self): '''検証が正しくない: redirect_uriが文字列以外で与えられている ''' # 準備 invalid_redirect_uri = int(71) # 実行・検証 fb = Fitbit(client_id=self.fake_client_id, client_secret=self.fake_client_secret, auth_code=self.fake_auth_code) with pytest.raises(TypeError, match='"redirect_uri" type must be str.'): fb.fetch_tokens(invalid_redirect_uri)
def test_invalid_date_not_abide_by_format(self): '''検証が正しくない: dateが文字列であるが指定したフォーマットに従っていない ''' # 準備 fake_category = 'activities' invalid_date = '20210925' # 実行・検証 fb = Fitbit(client_id=self.fake_client_id, client_secret=self.fake_client_secret, access_token=self.fake_access_token) with pytest.raises(ValueError, match='"date" must be yyyy-mm-dd.'): fb.fetch_trace_data(fake_category, invalid_date)
def test_invalid_date_not_string(self): '''検証が正しくない: dateに文字列以外の型が与えられる ''' # 準備 fake_category = 'activities' invalid_date = 20210925 # 実行・検証 fb = Fitbit(client_id=self.fake_client_id, client_secret=self.fake_client_secret, access_token=self.fake_access_token) with pytest.raises(TypeError, match='"date" type must be str.'): fb.fetch_trace_data(fake_category, invalid_date)
def test_invalid_category_not_string(self): '''検証が正しくない: categoryに文字列以外の型が与えられる ''' # 準備 fake_date = '2021-09-25' invalid_category = 71 # 実行・検証 fb = Fitbit(client_id=self.fake_client_id, client_secret=self.fake_client_secret, access_token=self.fake_access_token) with pytest.raises(TypeError, match='"category" type must be str.'): fb.fetch_trace_data(invalid_category, fake_date)
def test_invalid_redirect_uri_not_uri_format(self): '''検証が正しくない: redirect_uriがuriのフォーマットに従っていない ''' # 準備 invalid_redirect_uri = 'not_uri_format_string' # 実行・検証 fb = Fitbit(client_id=self.fake_client_id, client_secret=self.fake_client_secret, auth_code=self.fake_auth_code) with pytest.raises(ValueError, match='"redirect_uri" must be a uri format.'): fb.fetch_tokens(invalid_redirect_uri)
def test_invalid_access_token_epierd(self): '''検証が正しくない: access_tokenの期限切れ ''' fake_body_type = 'fat' fake_value = 25.1 fake_date = '2021-11-01' fake_time = '13:00:01' fb = Fitbit(client_id=self.fake_client_id, client_secret=self.fake_client_secret, access_token=self.expierd_access_token) with pytest.raises(urllib.error.HTTPError, match='HTTP Error 401: Unauthorized'): fb.create_body_log(fake_body_type, fake_value, fake_date, fake_time)
def test_invalid_access_token_is_expired(self): '''検証が正しくない: access_tokenの期限切れ ''' # 準備 fake_category = 'activities' fake_date = '2021-09-25' # 実行・検証 fb = Fitbit(client_id=self.fake_client_id, client_secret=self.fake_client_secret, access_token=self.bad_access_token) with pytest.raises(urllib.error.HTTPError, match='HTTP Error 401: Unauthorized'): fb.fetch_trace_data(fake_category, fake_date)
def test_invalid_category_bad_string(self): '''検証が正しくない: categoryにて"activities", "foods", "sleep"以外の値が与えられる ''' # 準備 fake_date = '2021-09-25' invalid_category = 'fake_category' # 実行・検証 fb = Fitbit(client_id=self.fake_client_id, client_secret=self.fake_client_secret, access_token=self.fake_access_token) with pytest.raises( ValueError, match='Please input "activities" or "foods" or "sleep"'): fb.fetch_trace_data(invalid_category, fake_date)
def run(prj: Union[None, str] = None) -> None: '''実行日の前日の健康データを各APIから取得・更新しgcsへデータを保存する(ローカル環境で実行する場合はこちら) Args: prj (Union[None, str], optional): 関数を実行する環境,未入力(None)ならばローカルとする. ''' # 設定 print('project env: {}'.format('local' if prj is None else 'GCP')) additional_path = '' if prj is None else '/tmp/' day = pd.Timestamp.today(tz='Asia/Tokyo') - DateOffset(days=1) day_str = day.strftime('%Y-%m-%d') # 一時的にファイルを保存するディレクトリを用意(cloud functions限定) if prj is not None: os.makedirs('/tmp/data/health_planet/', exist_ok=True) os.makedirs('/tmp/data/fitbit/activities/', exist_ok=True) os.makedirs('/tmp/data/fitbit/foods/', exist_ok=True) os.makedirs('/tmp/data/fitbit/sleep/', exist_ok=True) os.makedirs('/tmp/data/ring_fit_adventure/', exist_ok=True) # secret managerから値を取得 if prj is None: ini_path = './local.ini' config_ini = configparser.ConfigParser() config_ini.read(ini_path, encoding='utf-8') api_connect_values = { 'hp-access-token': config_ini.get('HEALTH PLANET', 'access-token'), 'fb-client-id': config_ini.get('FITBIT', 'client-id'), 'fb-client-secret': config_ini.get('FITBIT', 'client-secret'), 'fb-access-token': config_ini.get('FITBIT', 'access-token'), 'fb-refresh-token': config_ini.get('FITBIT', 'refresh-token'), 'tw-user-id': config_ini.get('TWITTER', 'user-id'), 'tw-beare-token': config_ini.get('TWITTER', 'escaped-bearer-token') } else: secret_manager_client = secretmanager.SecretManagerServiceClient() api_connect_values = {} for k in ['hp-access-token', 'fb-client-id', 'fb-client-secret', 'fb-access-token', 'fb-refresh-token', 'tw-user-id', 'tw-beare-token']: name = secret_manager_client.secret_version_path(prj, k, 'latest') response = secret_manager_client.access_secret_version(request={'name': name}) api_connect_values[k] = response.payload.data.decode('utf-8') # Health Planetから体組成データを取得 hp = HealthPlanet( access_token=api_connect_values['hp-access-token'] ) body_compositions = hp.fetch_body_composition_data(day_str, day_str) # 体組成データを保存 hp_path = additional_path + 'data/health_planet/{}.json'.format(day_str) with open(hp_path, 'w') as jsonfile: jsonfile.write(json.dumps(body_compositions)) # 体組成データをgcsへ転送 try: store_gcs(hp_path, 'health_planet/{}.json'.format(day_str)) os.remove(hp_path) except Exception: print(traceback.format_exc()) # Fitbitから運動・食事・睡眠データを取得し保存,転送 fb = Fitbit( client_id=api_connect_values['fb-client-id'], client_secret=api_connect_values['fb-client-secret'], access_token=api_connect_values['hp-access-token'], refresh_token=api_connect_values['fb-refresh-token'] ) for c in ['activities', 'foods', 'sleep']: # 取得 try: data = fb.fetch_trace_data(c, day_str) except urllib.error.HTTPError: print('execute method "refresh_access_token"') fb.refresh_access_token() if prj is None: # 実行環境がローカルであればiniファイルを上書き config_ini.set('FITBIT', 'access-token', fb.access_token) config_ini.set('FITBIT', 'refresh-token', fb.refresh_token) with open(ini_path, 'w') as f: config_ini.write(f) else: # 実行環境がcloud functions上であればsecretに新たなveersionを追加 # NOTE: 現時点の最新versionを停止した後に再取得したトークンの値をsecretに追加する tmp_values = { 'fb-access-token': fb.access_token, 'fb-refresh-token': fb.refresh_token } for k in ['fb-access-token', 'fb-refresh-token']: name = secret_manager_client.secret_version_path(prj, k, 'latest') v_response = secret_manager_client.get_secret_version(request={'name': name}) response = secret_manager_client.disable_secret_version(request={'name': v_response.name}) parent = secret_manager_client.secret_path(prj, k) response = secret_manager_client.add_secret_version( request={'parent': parent, 'payload': {'data': tmp_values[k].encode('utf-8')}} ) data = fb.fetch_trace_data(c, day_str) # 保存 fb_path = additional_path + 'data/fitbit/{}/{}.json'.format(c, day_str) with open(fb_path, 'w') as jsonfile: jsonfile.write(json.dumps(data)) # 転送 fb_gcs_path = 'fitbit/{}/{}.json'.format(c, day_str) try: store_gcs(fb_path, fb_gcs_path) os.remove(fb_path) except Exception: print(traceback.format_exc()) # Fitbitに体組成データの転送 if prj is not None: body_compositions_data = body_compositions['data'] if len(body_compositions_data) > 0: for i in body_compositions_data: created_datetime = pd.to_datetime(i['date']).strftime('%Y-%m-%d %H:%M') splited_created_datetime = created_datetime.split(' ') fb.create_body_log( body_type='weight' if i['tag'] == '6021' else 'fat', value=float(i['keydata']), created_date=splited_created_datetime[0], created_time=splited_created_datetime[1] + ':00' ) else: print('body compositions is empty.') # Twitterからリングフィットの実績画像URLを取得 tw = Twitter(api_connect_values['tw-user-id'], api_connect_values['tw-beare-token']) figure_urls = tw.search_ringfitadventure_results(day_str) if len(figure_urls) > 0: for u in figure_urls: figure_name = day_str + '_' + u.replace('https://pbs.twimg.com/media/', '') figure_path = additional_path + 'data/ring_fit_adventure/' + figure_name urllib.request.urlretrieve(u, figure_path) try: figure_gcs_path = 'ring_fit_adventure/' + figure_name store_gcs(figure_path, figure_gcs_path) except Exception: print('figures are saved local folder.') else: print('ringfitadventures results are nothing.')