blob: ab63d24f4c7adca06e8c2757a28ee8b73a02411d [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (C) 2008, 2009 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This module is used for version 2 of the Google Data APIs.
__author__ = 'j.s@google.com (Jeff Scudder)'
import unittest
import gdata.client
import gdata.gauth
import gdata.data
import atom.mock_http_core
import StringIO
class ClientLoginTest(unittest.TestCase):
def test_token_request(self):
client = gdata.client.GDClient()
client.http_client = atom.mock_http_core.SettableHttpClient(200, 'OK',
'SID=DQAAAGgA...7Zg8CTN\n'
'LSID=DQAAAGsA...lk8BBbG\n'
'Auth=DQAAAGgA...dk3fA5N', {'Content-Type': 'text/plain'})
token = client.request_client_login_token('email', 'pw', 'cp', 'test')
self.assert_(isinstance(token, gdata.gauth.ClientLoginToken))
self.assertEqual(token.token_string, 'DQAAAGgA...dk3fA5N')
# Test a server response without a ClientLogin token.`
client.http_client.set_response(200, 'OK', 'SID=12345\nLSID=34567', {})
self.assertRaises(gdata.client.ClientLoginTokenMissing,
client.request_client_login_token, 'email', 'pw', '', '')
# Test a 302 redirect from the server on a login request.
client.http_client.set_response(302, 'ignored', '', {})
# TODO: change the exception class to one in gdata.client.
self.assertRaises(gdata.client.BadAuthenticationServiceURL,
client.request_client_login_token, 'email', 'pw', '', '')
# Test a CAPTCHA challenge from the server
client.http_client.set_response(403, 'Access Forbidden',
'Url=http://www.google.com/login/captcha\n'
'Error=CaptchaRequired\n'
'CaptchaToken=DQAAAGgA...dkI1LK9\n'
# TODO: verify this sample CAPTCHA URL matches an
# actual challenge from the server.
'CaptchaUrl=Captcha?ctoken=HiteT4bVoP6-yFkHPibe7O9EqxeiI7lUSN', {})
try:
token = client.request_client_login_token('email', 'pw', '', '')
self.fail('should raise a CaptchaChallenge on a 403 with a '
'CaptchRequired error.')
except gdata.client.CaptchaChallenge, challenge:
self.assertEquals(challenge.captcha_url,
'http://www.google.com/accounts/'
'Captcha?ctoken=HiteT4bVoP6-yFkHPibe7O9EqxeiI7lUSN')
self.assertEquals(challenge.captcha_token, 'DQAAAGgA...dkI1LK9')
# Test an unexpected response, a 404 for example.
client.http_client.set_response(404, 'ignored', '', {})
self.assertRaises(gdata.client.ClientLoginFailed,
client.request_client_login_token, 'email', 'pw', '', '')
def test_client_login(self):
client = gdata.client.GDClient()
client.http_client = atom.mock_http_core.SettableHttpClient(200, 'OK',
'SID=DQAAAGgA...7Zg8CTN\n'
'LSID=DQAAAGsA...lk8BBbG\n'
'Auth=DQAAAGgA...dk3fA5N', {'Content-Type': 'text/plain'})
client.client_login('me@example.com', 'password', 'wise', 'unit test')
self.assert_(isinstance(client.auth_token, gdata.gauth.ClientLoginToken))
self.assertEqual(client.auth_token.token_string, 'DQAAAGgA...dk3fA5N')
class AuthSubTest(unittest.TestCase):
def test_get_and_upgrade_token(self):
client = gdata.client.GDClient()
client.http_client = atom.mock_http_core.SettableHttpClient(200, 'OK',
'Token=UpgradedTokenVal\n'
'Extra data', {'Content-Type': 'text/plain'})
page_url = 'http://example.com/showcalendar.html?token=CKF50YzIHxCTKMAg'
client.auth_token = gdata.gauth.AuthSubToken.from_url(page_url)
self.assert_(isinstance(client.auth_token, gdata.gauth.AuthSubToken))
self.assertEqual(client.auth_token.token_string, 'CKF50YzIHxCTKMAg')
upgraded = client.upgrade_token()
self.assert_(isinstance(client.auth_token, gdata.gauth.AuthSubToken))
self.assertEqual(client.auth_token.token_string, 'UpgradedTokenVal')
self.assertEqual(client.auth_token, upgraded)
# Ensure passing in a token returns without modifying client's auth_token.
client.http_client.set_response(200, 'OK', 'Token=4567', {})
upgraded = client.upgrade_token(
gdata.gauth.AuthSubToken.from_url('?token=1234'))
self.assertEqual(upgraded.token_string, '4567')
self.assertEqual(client.auth_token.token_string, 'UpgradedTokenVal')
self.assertNotEqual(client.auth_token, upgraded)
# Test exception cases
client.auth_token = None
self.assertRaises(gdata.client.UnableToUpgradeToken, client.upgrade_token,
None)
self.assertRaises(gdata.client.UnableToUpgradeToken, client.upgrade_token)
def test_revoke_token(self):
client = gdata.client.GDClient()
client.http_client = atom.mock_http_core.SettableHttpClient(
200, 'OK', '', {})
page_url = 'http://example.com/showcalendar.html?token=CKF50YzIHxCTKMAg'
client.auth_token = gdata.gauth.AuthSubToken.from_url(page_url)
deleted = client.revoke_token()
self.assert_(deleted)
self.assertEqual(
client.http_client.last_request.headers['Authorization'],
'AuthSub token=CKF50YzIHxCTKMAg')
class OAuthTest(unittest.TestCase):
def test_hmac_flow(self):
client = gdata.client.GDClient()
client.http_client = atom.mock_http_core.SettableHttpClient(
200, 'OK', 'oauth_token=ab3cd9j4ks7&oauth_token_secret=ZXhhbXBsZS',
{})
request_token = client.get_oauth_token(
['http://example.com/service'], 'http://example.net/myapp',
'consumer', consumer_secret='secret')
# Check that the response was correctly parsed.
self.assertEqual(request_token.token, 'ab3cd9j4ks7')
self.assertEqual(request_token.token_secret, 'ZXhhbXBsZS')
self.assertEqual(request_token.auth_state, gdata.gauth.REQUEST_TOKEN)
# Also check the Authorization header which was sent in the request.
auth_header = client.http_client.last_request.headers['Authorization']
self.assert_('OAuth' in auth_header)
self.assert_(
'oauth_callback="http%3A%2F%2Fexample.net%2Fmyapp"' in auth_header)
self.assert_('oauth_version="1.0"' in auth_header)
self.assert_('oauth_signature_method="HMAC-SHA1"' in auth_header)
self.assert_('oauth_consumer_key="consumer"' in auth_header)
# Check generation of the authorization URL.
authorize_url = request_token.generate_authorization_url()
self.assert_(str(authorize_url).startswith(
'https://www.google.com/accounts/OAuthAuthorizeToken'))
self.assert_('oauth_token=ab3cd9j4ks7' in str(authorize_url))
# Check that the token information from the browser's URL is parsed.
redirected_url = (
'http://example.net/myapp?oauth_token=CKF5zz&oauth_verifier=Xhhbas')
gdata.gauth.authorize_request_token(request_token, redirected_url)
self.assertEqual(request_token.token, 'CKF5zz')
self.assertEqual(request_token.verifier, 'Xhhbas')
self.assertEqual(request_token.auth_state,
gdata.gauth.AUTHORIZED_REQUEST_TOKEN)
# Check that the token upgrade response was correctly parsed.
client.http_client.set_response(
200, 'OK', 'oauth_token=3cd9Fj417&oauth_token_secret=Xhrh6bXBs', {})
access_token = client.get_access_token(request_token)
self.assertEqual(request_token.token, '3cd9Fj417')
self.assertEqual(request_token.token_secret, 'Xhrh6bXBs')
self.assert_(request_token.verifier is None)
self.assertEqual(request_token.auth_state, gdata.gauth.ACCESS_TOKEN)
self.assertEqual(request_token.token, access_token.token)
self.assertEqual(request_token.token_secret, access_token.token_secret)
self.assert_(access_token.verifier is None)
self.assertEqual(request_token.auth_state, access_token.auth_state)
# Also check the Authorization header which was sent in the request.
auth_header = client.http_client.last_request.headers['Authorization']
self.assert_('OAuth' in auth_header)
self.assert_('oauth_callback="' not in auth_header)
self.assert_('oauth_version="1.0"' in auth_header)
self.assert_('oauth_verifier="Xhhbas"' in auth_header)
self.assert_('oauth_signature_method="HMAC-SHA1"' in auth_header)
self.assert_('oauth_consumer_key="consumer"' in auth_header)
class RequestTest(unittest.TestCase):
def test_simple_request(self):
client = gdata.client.GDClient()
client.http_client = atom.mock_http_core.EchoHttpClient()
response = client.request('GET', 'https://example.com/test')
self.assertEqual(response.getheader('Echo-Host'), 'example.com:None')
self.assertEqual(response.getheader('Echo-Uri'), '/test')
self.assertEqual(response.getheader('Echo-Scheme'), 'https')
self.assertEqual(response.getheader('Echo-Method'), 'GET')
http_request = atom.http_core.HttpRequest(
uri=atom.http_core.Uri(scheme='http', host='example.net', port=8080),
method='POST', headers={'X': 1})
http_request.add_body_part('test', 'text/plain')
response = client.request(http_request=http_request)
self.assertEqual(response.getheader('Echo-Host'), 'example.net:8080')
# A Uri with path set to None should default to /.
self.assertEqual(response.getheader('Echo-Uri'), '/')
self.assertEqual(response.getheader('Echo-Scheme'), 'http')
self.assertEqual(response.getheader('Echo-Method'), 'POST')
self.assertEqual(response.getheader('Content-Type'), 'text/plain')
self.assertEqual(response.getheader('X'), '1')
self.assertEqual(response.read(), 'test')
# Use the same request object from above, but overwrite the request path
# by passing in a URI.
response = client.request(uri='/new/path?p=1', http_request=http_request)
self.assertEqual(response.getheader('Echo-Host'), 'example.net:8080')
self.assertEqual(response.getheader('Echo-Uri'), '/new/path?p=1')
self.assertEqual(response.getheader('Echo-Scheme'), 'http')
self.assertEqual(response.read(), 'test')
def test_gdata_version_header(self):
client = gdata.client.GDClient()
client.http_client = atom.mock_http_core.EchoHttpClient()
response = client.request('GET', 'http://example.com')
self.assertEqual(response.getheader('GData-Version'), None)
client.api_version = '2'
response = client.request('GET', 'http://example.com')
self.assertEqual(response.getheader('GData-Version'), '2')
def test_redirects(self):
client = gdata.client.GDClient()
client.http_client = atom.mock_http_core.MockHttpClient()
# Add the redirect response for the initial request.
first_request = atom.http_core.HttpRequest('http://example.com/1',
'POST')
client.http_client.add_response(first_request, 302, None,
{'Location': 'http://example.com/1?gsessionid=12'})
second_request = atom.http_core.HttpRequest(
'http://example.com/1?gsessionid=12', 'POST')
client.http_client.AddResponse(second_request, 200, 'OK', body='Done')
response = client.Request('POST', 'http://example.com/1')
self.assertEqual(response.status, 200)
self.assertEqual(response.reason, 'OK')
self.assertEqual(response.read(), 'Done')
redirect_loop_request = atom.http_core.HttpRequest(
'http://example.com/2?gsessionid=loop', 'PUT')
client.http_client.add_response(redirect_loop_request, 302, None,
{'Location': 'http://example.com/2?gsessionid=loop'})
try:
response = client.request(method='PUT', uri='http://example.com/2?gsessionid=loop')
self.fail('Loop URL should have redirected forever.')
except gdata.client.RedirectError, err:
self.assert_(str(err).startswith('Too many redirects from server'))
def test_lowercase_location(self):
client = gdata.client.GDClient()
client.http_client = atom.mock_http_core.MockHttpClient()
# Add the redirect response for the initial request.
first_request = atom.http_core.HttpRequest('http://example.com/1',
'POST')
# In some environments, notably App Engine, the HTTP headers which come
# back from a server will be normalized to all lowercase.
client.http_client.add_response(first_request, 302, None,
{'location': 'http://example.com/1?gsessionid=12'})
second_request = atom.http_core.HttpRequest(
'http://example.com/1?gsessionid=12', 'POST')
client.http_client.AddResponse(second_request, 200, 'OK', body='Done')
response = client.Request('POST', 'http://example.com/1')
self.assertEqual(response.status, 200)
self.assertEqual(response.reason, 'OK')
self.assertEqual(response.read(), 'Done')
def test_exercise_exceptions(self):
# TODO
pass
def test_converter_vs_desired_class(self):
def bad_converter(string):
return 1
class TestClass(atom.core.XmlElement):
_qname = '{http://www.w3.org/2005/Atom}entry'
client = gdata.client.GDClient()
client.http_client = atom.mock_http_core.EchoHttpClient()
test_entry = gdata.data.GDEntry()
result = client.post(test_entry, 'http://example.com')
self.assert_(isinstance(result, gdata.data.GDEntry))
result = client.post(test_entry, 'http://example.com', converter=bad_converter)
self.assertEquals(result, 1)
result = client.post(test_entry, 'http://example.com', desired_class=TestClass)
self.assert_(isinstance(result, TestClass))
class QueryTest(unittest.TestCase):
def test_query_modifies_request(self):
request = atom.http_core.HttpRequest()
gdata.client.Query(
text_query='foo', categories=['a', 'b']).modify_request(request)
# categories param from above is named category in URL
self.assertEqual(request.uri.query, {'q': 'foo', 'category': 'a,b'})
def test_client_uses_query_modification(self):
"""If the Query is passed as an unexpected param it should apply"""
client = gdata.client.GDClient()
client.http_client = atom.mock_http_core.EchoHttpClient()
query = gdata.client.Query(max_results=7)
client.http_client = atom.mock_http_core.SettableHttpClient(
201, 'CREATED', gdata.data.GDEntry().ToString(), {})
response = client.get('https://example.com/foo', a_random_param=query)
self.assertEqual(
client.http_client.last_request.uri.query['max-results'], '7')
class VersionConversionTest(unittest.TestCase):
def test_use_default_version(self):
self.assertEquals(gdata.client.get_xml_version(None), 1)
def test_str_to_int_version(self):
self.assertEquals(gdata.client.get_xml_version('1'), 1)
self.assertEquals(gdata.client.get_xml_version('2'), 2)
self.assertEquals(gdata.client.get_xml_version('2.1.2'), 2)
self.assertEquals(gdata.client.get_xml_version('10.4'), 10)
class UpdateTest(unittest.TestCase):
"""Test Update/PUT"""
def test_update_uri_editlink(self):
"""Test that the PUT uri is grabbed from the entry's edit link"""
client = gdata.client.GDClient()
client.http_client = atom.mock_http_core.SettableHttpClient(
200, 'OK', gdata.data.GDEntry().ToString(), {})
entry = gdata.data.GDEntry()
entry.link.append(atom.data.Link(rel='edit', href='https://example.com/edit'))
response = client.update(entry)
request = client.http_client.last_request
self.assertEqual(str(client.http_client.last_request.uri),
'https://example.com/edit')
def test_update_uri(self):
"""Test that when passed, a uri overrides the entry's edit link"""
client = gdata.client.GDClient()
client.http_client = atom.mock_http_core.SettableHttpClient(
200, 'OK', gdata.data.GDEntry().ToString(), {})
entry = gdata.data.GDEntry()
entry.link.append(atom.data.Link(rel='edit', href='https://example.com/edit'))
response = client.update(entry, uri='https://example.com/test')
self.assertEqual(str(client.http_client.last_request.uri),
'https://example.com/test')
def suite():
return unittest.TestSuite((unittest.makeSuite(ClientLoginTest, 'test'),
unittest.makeSuite(AuthSubTest, 'test'),
unittest.makeSuite(OAuthTest, 'test'),
unittest.makeSuite(RequestTest, 'test'),
unittest.makeSuite(VersionConversionTest, 'test'),
unittest.makeSuite(QueryTest, 'test'),
unittest.makeSuite(UpdateTest, 'test')))
if __name__ == '__main__':
unittest.main()