18d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi#!/usr/bin/python2.4
28d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi#
38d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi# Copyright 2014 Google Inc. All rights reserved.
48d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi#
58d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi# Licensed under the Apache License, Version 2.0 (the "License");
68d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi# you may not use this file except in compliance with the License.
78d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi# You may obtain a copy of the License at
88d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi#
98d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi#      http://www.apache.org/licenses/LICENSE-2.0
108d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi#
118d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi# Unless required by applicable law or agreed to in writing, software
128d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi# distributed under the License is distributed on an "AS IS" BASIS,
138d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
148d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi# See the License for the specific language governing permissions and
158d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi# limitations under the License.
168d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
178d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
188d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi"""Oauth2client.file tests
198d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
208d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi KandoiUnit tests for oauth2client.file
218d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi"""
228d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
238d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi__author__ = 'jcgregorio@google.com (Joe Gregorio)'
248d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
258d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoiimport copy
268d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoiimport datetime
278d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoiimport json
288d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoiimport os
298d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoiimport pickle
308d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoiimport stat
318d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoiimport tempfile
328d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoiimport unittest
338d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
348d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoifrom .http_mock import HttpMockSequence
358d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoiimport six
368d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
378d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoifrom oauth2client import file
388d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoifrom oauth2client import locked_file
398d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoifrom oauth2client import multistore_file
408d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoifrom oauth2client import util
418d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoifrom oauth2client.client import AccessTokenCredentials
428d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoifrom oauth2client.client import OAuth2Credentials
438d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoifrom six.moves import http_client
448d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoitry:
458d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  # Python2
468d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  from future_builtins import oct
478d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoiexcept:
488d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  pass
498d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
508d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
518d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi KandoiFILENAME = tempfile.mktemp('oauth2client_test.data')
528d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
538d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
548d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoiclass OAuth2ClientFileTests(unittest.TestCase):
558d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
568d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def tearDown(self):
578d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    try:
588d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      os.unlink(FILENAME)
598d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    except OSError:
608d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      pass
618d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
628d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def setUp(self):
638d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    try:
648d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      os.unlink(FILENAME)
658d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    except OSError:
668d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      pass
678d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
688d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def create_test_credentials(self, client_id='some_client_id',
698d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi                              expiration=None):
708d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    access_token = 'foo'
718d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    client_secret = 'cOuDdkfjxxnv+'
728d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    refresh_token = '1/0/a.df219fjls0'
738d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    token_expiry = expiration or datetime.datetime.utcnow()
748d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    token_uri = 'https://www.google.com/accounts/o8/oauth2/token'
758d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    user_agent = 'refresh_checker/1.0'
768d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
778d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = OAuth2Credentials(
788d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        access_token, client_id, client_secret,
798d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        refresh_token, token_expiry, token_uri,
808d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        user_agent)
818d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    return credentials
828d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
838d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_non_existent_file_storage(self):
848d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s = file.Storage(FILENAME)
858d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = s.get()
868d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals(None, credentials)
878d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
888d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_no_sym_link_credentials(self):
898d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    if hasattr(os, 'symlink'):
908d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      SYMFILENAME = FILENAME + '.sym'
918d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      os.symlink(FILENAME, SYMFILENAME)
928d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      s = file.Storage(SYMFILENAME)
938d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      try:
948d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        s.get()
958d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        self.fail('Should have raised an exception.')
968d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      except file.CredentialsFileSymbolicLinkError:
978d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        pass
988d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      finally:
998d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        os.unlink(SYMFILENAME)
1008d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1018d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_pickle_and_json_interop(self):
1028d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    # Write a file with a pickled OAuth2Credentials.
1038d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = self.create_test_credentials()
1048d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1058d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    f = open(FILENAME, 'wb')
1068d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    pickle.dump(credentials, f)
1078d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    f.close()
1088d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1098d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    # Storage should be not be able to read that object, as the capability to
1108d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    # read and write credentials as pickled objects has been removed.
1118d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s = file.Storage(FILENAME)
1128d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    read_credentials = s.get()
1138d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals(None, read_credentials)
1148d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1158d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    # Now write it back out and confirm it has been rewritten as JSON
1168d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s.put(credentials)
1178d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    with open(FILENAME) as f:
1188d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      data = json.load(f)
1198d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1208d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals(data['access_token'], 'foo')
1218d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals(data['_class'], 'OAuth2Credentials')
1228d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals(data['_module'], OAuth2Credentials.__module__)
1238d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1248d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_token_refresh_store_expired(self):
1258d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    expiration = datetime.datetime.utcnow() - datetime.timedelta(minutes=15)
1268d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = self.create_test_credentials(expiration=expiration)
1278d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1288d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s = file.Storage(FILENAME)
1298d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s.put(credentials)
1308d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = s.get()
1318d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    new_cred = copy.copy(credentials)
1328d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    new_cred.access_token = 'bar'
1338d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s.put(new_cred)
1348d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1358d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    access_token = '1/3w'
1368d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    token_response = {'access_token': access_token, 'expires_in': 3600}
1378d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    http = HttpMockSequence([
1388d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        ({'status': '200'}, json.dumps(token_response).encode('utf-8')),
1398d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    ])
1408d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1418d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials._refresh(http.request)
1428d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals(credentials.access_token, access_token)
1438d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1448d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_token_refresh_store_expires_soon(self):
1458d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    # Tests the case where an access token that is valid when it is read from
1468d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    # the store expires before the original request succeeds.
1478d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
1488d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = self.create_test_credentials(expiration=expiration)
1498d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1508d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s = file.Storage(FILENAME)
1518d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s.put(credentials)
1528d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = s.get()
1538d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    new_cred = copy.copy(credentials)
1548d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    new_cred.access_token = 'bar'
1558d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s.put(new_cred)
1568d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1578d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    access_token = '1/3w'
1588d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    token_response = {'access_token': access_token, 'expires_in': 3600}
1598d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    http = HttpMockSequence([
1608d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        ({'status': str(http_client.UNAUTHORIZED)}, b'Initial token expired'),
1618d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        ({'status': str(http_client.UNAUTHORIZED)}, b'Store token expired'),
1628d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        ({'status': str(http_client.OK)},
1638d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi         json.dumps(token_response).encode('utf-8')),
1648d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        ({'status': str(http_client.OK)},
1658d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi         b'Valid response to original request')
1668d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    ])
1678d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1688d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials.authorize(http)
1698d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    http.request('https://example.com')
1708d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEqual(credentials.access_token, access_token)
1718d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1728d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_token_refresh_good_store(self):
1738d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
1748d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = self.create_test_credentials(expiration=expiration)
1758d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1768d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s = file.Storage(FILENAME)
1778d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s.put(credentials)
1788d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = s.get()
1798d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    new_cred = copy.copy(credentials)
1808d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    new_cred.access_token = 'bar'
1818d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s.put(new_cred)
1828d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1838d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials._refresh(lambda x: x)
1848d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals(credentials.access_token, 'bar')
1858d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1868d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_token_refresh_stream_body(self):
1878d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
1888d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = self.create_test_credentials(expiration=expiration)
1898d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1908d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s = file.Storage(FILENAME)
1918d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s.put(credentials)
1928d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = s.get()
1938d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    new_cred = copy.copy(credentials)
1948d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    new_cred.access_token = 'bar'
1958d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s.put(new_cred)
1968d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
1978d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    valid_access_token = '1/3w'
1988d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    token_response = {'access_token': valid_access_token, 'expires_in': 3600}
1998d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    http = HttpMockSequence([
2008d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        ({'status': str(http_client.UNAUTHORIZED)}, b'Initial token expired'),
2018d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        ({'status': str(http_client.UNAUTHORIZED)}, b'Store token expired'),
2028d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        ({'status': str(http_client.OK)},
2038d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi         json.dumps(token_response).encode('utf-8')),
2048d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        ({'status': str(http_client.OK)}, 'echo_request_body')
2058d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    ])
2068d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2078d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    body = six.StringIO('streaming body')
2088d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2098d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials.authorize(http)
2108d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    _, content = http.request('https://example.com', body=body)
2118d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEqual(content, 'streaming body')
2128d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEqual(credentials.access_token, valid_access_token)
2138d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2148d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_credentials_delete(self):
2158d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = self.create_test_credentials()
2168d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2178d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s = file.Storage(FILENAME)
2188d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s.put(credentials)
2198d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = s.get()
2208d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertNotEquals(None, credentials)
2218d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s.delete()
2228d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = s.get()
2238d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals(None, credentials)
2248d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2258d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_access_token_credentials(self):
2268d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    access_token = 'foo'
2278d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    user_agent = 'refresh_checker/1.0'
2288d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2298d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = AccessTokenCredentials(access_token, user_agent)
2308d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2318d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    s = file.Storage(FILENAME)
2328d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = s.put(credentials)
2338d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = s.get()
2348d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2358d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertNotEquals(None, credentials)
2368d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals('foo', credentials.access_token)
2378d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    mode = os.stat(FILENAME).st_mode
2388d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2398d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    if os.name == 'posix':
2408d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      self.assertEquals('0o600', oct(stat.S_IMODE(os.stat(FILENAME).st_mode)))
2418d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2428d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_read_only_file_fail_lock(self):
2438d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = self.create_test_credentials()
2448d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2458d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    open(FILENAME, 'a+b').close()
2468d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    os.chmod(FILENAME, 0o400)
2478d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2488d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store = multistore_file.get_credential_storage(
2498d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        FILENAME,
2508d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        credentials.client_id,
2518d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        credentials.user_agent,
2528d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        ['some-scope', 'some-other-scope'])
2538d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2548d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store.put(credentials)
2558d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    if os.name == 'posix':
2568d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      self.assertTrue(store._multistore._read_only)
2578d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    os.chmod(FILENAME, 0o600)
2588d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2598d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_multistore_no_symbolic_link_files(self):
2608d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    if hasattr(os, 'symlink'):
2618d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      SYMFILENAME = FILENAME + 'sym'
2628d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      os.symlink(FILENAME, SYMFILENAME)
2638d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      store = multistore_file.get_credential_storage(
2648d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi          SYMFILENAME,
2658d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi          'some_client_id',
2668d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi          'user-agent/1.0',
2678d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi          ['some-scope', 'some-other-scope'])
2688d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      try:
2698d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        store.get()
2708d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        self.fail('Should have raised an exception.')
2718d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      except locked_file.CredentialsFileSymbolicLinkError:
2728d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        pass
2738d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      finally:
2748d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        os.unlink(SYMFILENAME)
2758d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2768d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_multistore_non_existent_file(self):
2778d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store = multistore_file.get_credential_storage(
2788d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        FILENAME,
2798d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        'some_client_id',
2808d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        'user-agent/1.0',
2818d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        ['some-scope', 'some-other-scope'])
2828d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2838d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = store.get()
2848d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals(None, credentials)
2858d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2868d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_multistore_file(self):
2878d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = self.create_test_credentials()
2888d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2898d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store = multistore_file.get_credential_storage(
2908d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        FILENAME,
2918d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        credentials.client_id,
2928d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        credentials.user_agent,
2938d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        ['some-scope', 'some-other-scope'])
2948d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2958d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store.put(credentials)
2968d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = store.get()
2978d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
2988d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertNotEquals(None, credentials)
2998d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals('foo', credentials.access_token)
3008d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3018d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store.delete()
3028d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = store.get()
3038d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3048d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals(None, credentials)
3058d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3068d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    if os.name == 'posix':
3078d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi      self.assertEquals('0o600', oct(stat.S_IMODE(os.stat(FILENAME).st_mode)))
3088d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3098d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_multistore_file_custom_key(self):
3108d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = self.create_test_credentials()
3118d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3128d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    custom_key = {'myapp': 'testing', 'clientid': 'some client'}
3138d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store = multistore_file.get_credential_storage_custom_key(
3148d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        FILENAME, custom_key)
3158d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3168d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store.put(credentials)
3178d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    stored_credentials = store.get()
3188d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3198d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertNotEquals(None, stored_credentials)
3208d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEqual(credentials.access_token, stored_credentials.access_token)
3218d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3228d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store.delete()
3238d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    stored_credentials = store.get()
3248d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3258d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals(None, stored_credentials)
3268d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3278d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_multistore_file_custom_string_key(self):
3288d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = self.create_test_credentials()
3298d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3308d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    # store with string key
3318d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store = multistore_file.get_credential_storage_custom_string_key(
3328d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        FILENAME, 'mykey')
3338d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3348d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store.put(credentials)
3358d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    stored_credentials = store.get()
3368d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3378d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertNotEquals(None, stored_credentials)
3388d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEqual(credentials.access_token, stored_credentials.access_token)
3398d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3408d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    # try retrieving with a dictionary
3418d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store_dict = multistore_file.get_credential_storage_custom_string_key(
3428d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        FILENAME, {'key': 'mykey'})
3438d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    stored_credentials = store.get()
3448d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertNotEquals(None, stored_credentials)
3458d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEqual(credentials.access_token, stored_credentials.access_token)
3468d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3478d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store.delete()
3488d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    stored_credentials = store.get()
3498d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3508d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals(None, stored_credentials)
3518d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3528d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_multistore_file_backwards_compatibility(self):
3538d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = self.create_test_credentials()
3548d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    scopes = ['scope1', 'scope2']
3558d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3568d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    # store the credentials using the legacy key method
3578d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store = multistore_file.get_credential_storage(
3588d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        FILENAME, 'client_id', 'user_agent', scopes)
3598d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store.put(credentials)
3608d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3618d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    # retrieve the credentials using a custom key that matches the legacy key
3628d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    key = {'clientId': 'client_id', 'userAgent': 'user_agent',
3638d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi           'scope': util.scopes_to_string(scopes)}
3648d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store = multistore_file.get_credential_storage_custom_key(FILENAME, key)
3658d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    stored_credentials = store.get()
3668d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3678d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEqual(credentials.access_token, stored_credentials.access_token)
3688d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3698d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  def test_multistore_file_get_all_keys(self):
3708d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    # start with no keys
3718d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    keys = multistore_file.get_all_credential_keys(FILENAME)
3728d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals([], keys)
3738d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3748d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    # store credentials
3758d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = self.create_test_credentials(client_id='client1')
3768d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    custom_key = {'myapp': 'testing', 'clientid': 'client1'}
3778d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store1 = multistore_file.get_credential_storage_custom_key(
3788d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        FILENAME, custom_key)
3798d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store1.put(credentials)
3808d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3818d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    keys = multistore_file.get_all_credential_keys(FILENAME)
3828d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals([custom_key], keys)
3838d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3848d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    # store more credentials
3858d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    credentials = self.create_test_credentials(client_id='client2')
3868d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    string_key = 'string_key'
3878d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store2 = multistore_file.get_credential_storage_custom_string_key(
3888d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi        FILENAME, string_key)
3898d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store2.put(credentials)
3908d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3918d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    keys = multistore_file.get_all_credential_keys(FILENAME)
3928d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals(2, len(keys))
3938d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertTrue(custom_key in keys)
3948d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertTrue({'key': string_key} in keys)
3958d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
3968d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    # back to no keys
3978d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store1.delete()
3988d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    store2.delete()
3998d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    keys = multistore_file.get_all_credential_keys(FILENAME)
4008d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi    self.assertEquals([], keys)
4018d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
4028d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi
4038d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoiif __name__ == '__main__':
4048d2b206a675ec20ea07100c35df34e65ee1e45e8Ruchi Kandoi  unittest.main()
405