From 1561654b8db739f477b405b6cfecce2580abcf04 Mon Sep 17 00:00:00 2001 From: Andrew Flegg Date: Thu, 30 Dec 2010 16:05:39 +0000 Subject: [PATCH] Re-implement Facebook service to use OAuth2 and Graph API. This allows the requesting of additional permissions when authorising the account. Fixes MB#11103. --- package/debian/control | 3 +- package/debian/hermes.postinst | 2 +- package/src/oauth2.py | 132 ++++++++++++++++++++ .../src/org/maemo/hermes/engine/facebook/api.py | 64 ++++++++++ .../org/maemo/hermes/engine/facebook/provider.py | 100 +++++---------- .../org/maemo/hermes/engine/facebook/service.py | 25 ++-- package/test/automatic_tests.py | 5 +- package/test/integration/test_facebook.py | 36 ++++++ package/test/integration/test_oauth2.py | 41 ++++++ package/test/unit/test_facebook.py | 73 ++++++++--- 10 files changed, 375 insertions(+), 106 deletions(-) create mode 100644 package/src/oauth2.py create mode 100644 package/src/org/maemo/hermes/engine/facebook/api.py create mode 100644 package/test/integration/test_facebook.py create mode 100644 package/test/integration/test_oauth2.py diff --git a/package/debian/control b/package/debian/control index 2489622..3c08435 100644 --- a/package/debian/control +++ b/package/debian/control @@ -10,8 +10,7 @@ Architecture: all Depends: ${shlibs:Depends}, ${misc:Depends}, python-imaging | python2.5-imaging, python-osso | python2.5-osso, python-hildon | python2.5-hildon, python-simplejson, python-evolution, python-conic, - python-facebook (>= 0.svn20090225-0maemo2), python-evolution, - python-gobject (>= 2.16), gnome-python + python-evolution, python-gobject (>= 2.16), gnome-python Description: Enrich contacts' information from social networks Hermes, the Greek god of communication, will fill in the gaps in your contacts' address book. Photos and birthdays for your friends on diff --git a/package/debian/hermes.postinst b/package/debian/hermes.postinst index 5208d64..605c66e 100644 --- a/package/debian/hermes.postinst +++ b/package/debian/hermes.postinst @@ -1,7 +1,7 @@ #!/bin/sh set -e -gconftool-2 -s /apps/maemo/hermes/facebook_app 5916f12942feea4b3247d42a84371112 --type string +gconftool-2 -s /apps/maemo/hermes/facebook_key 5916f12942feea4b3247d42a84371112 --type string gconftool-2 -s /apps/maemo/hermes/facebook_secret 19f7538edd96b6870f2da7e84a6390a4 --type string gconftool-2 -s /apps/maemo/hermes/gravatar_email maemohermes@wendt.se --type string gconftool-2 -s /apps/maemo/hermes/gravatar_key b14ec179822b --type string diff --git a/package/src/oauth2.py b/package/src/oauth2.py new file mode 100644 index 0000000..f47dee0 --- /dev/null +++ b/package/src/oauth2.py @@ -0,0 +1,132 @@ +import webbrowser +import urllib, urllib2 +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +import urlparse, cgi + +class OAuth2: + """Implementation of OAuth2 protocol, as described by Facebook: + + http://developers.facebook.com/docs/authentication/#web_server_auth + + Uses a local web server to retrieve the code and hence the access token. + + Copyright (c) Andrew Flegg 2010. + Released under the Artistic Licence.""" + + + # ----------------------------------------------------------------------- + def __init__(self, client_id, client_secret, access_token = None): + '''Create a new OAuth 2.0 client, with the folowing arguments: + + client_id - Identifier of the application. + client_secret - Secret API key of the application. + access_token - Current access token, if any.''' + + self._client_id = client_id + self._client_secret = client_secret + self._access_token = access_token + + + # ----------------------------------------------------------------------- + def authorise(self, authorise_url, access_token_url, args = None): + '''Open a browser window to allow the user to log in and + authorise this client.''' + + redirect_uri = 'http://localhost:3435/success' + webbrowser.open_new('%s?client_id=%s&redirect_uri=%s&%s' % (authorise_url, self._client_id, redirect_uri, args and urllib.urlencode(args) or '')) + handler = OAuthCodeHandler() + code = handler.run() + + result = urllib2.urlopen('%s?client_id=%s&redirect_uri=%s&client_secret=%s&code=%s' % (access_token_url, self._client_id, redirect_uri, self._client_secret, code)).read() + params = cgi.parse_qs(result) + if 'access_token' in params: + self._access_token = params['access_token'][0] + else: + print result, params + raise Exception('Unable to retrieve access_token from Facebook') + + + # ----------------------------------------------------------------------- + def request(self, url, args = None): + '''Make an authenticated request to the given URL. If no + access token is currently set, an exception will be thrown. + + An optional dictionary of parameters can be specified.''' + + if not self._access_token: + raise Exception("Unauthorised") + + query_url = '%s?access_token=%s&%s' % (url, self._access_token, args and urllib.urlencode(args) or '') +# print query_url + result = urllib2.urlopen(query_url).read() + return result + + + # ----------------------------------------------------------------------- + def get_access_token(self): + """Get the access token in use by this OAuth 2.0 client, + so that it can be persisted.""" + + return self._access_token + + +# --------------------------------------------------------------------------- +class OAuthCodeHandler(HTTPServer): + """Handles the response from an OAuth2 handler and allows the + retrieval of the code.""" + + # ----------------------------------------------------------------------- + def __init__(self, success_response = '

Success

Please now close this window.

', + failure_response = '

Failed

%s

'): + '''Create a new handler, with optional overrides of the + success and failure response messages.''' + + HTTPServer.__init__(self, ('127.0.0.1', 3435), OAuthHttpRequestHandler) + self._success_response = success_response + self._failure_response = failure_response + self._code = None + + + # ----------------------------------------------------------------------- + def run(self): + '''Start a server and wait for a redirect. Return the code + if/when successful.''' + + self.handle_request() + return self._code + + + # ----------------------------------------------------------------------- + def set_code(self, code): + '''Called by the handler to feed us back the code.''' + + self._code = code + + def get_success_response(self): + return self._success_response + + def get_failure_response(self): + return self._failure_response + + +# --------------------------------------------------------------------------- +class OAuthHttpRequestHandler(BaseHTTPRequestHandler): + def do_GET(self): + qs = urlparse.urlparse(self.path).query + params = cgi.parse_qs(qs) + + if 'code' in params: + self.server.set_code(params['code'][0]) + + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + self.wfile.write(self.server.get_success_response()) + else: + print qs, params + self.send_response(500) + self.send_header('Content-type', 'text/html') + self.end_headers() + self.wfile.write(self.server.get_failure_response()) + + return diff --git a/package/src/org/maemo/hermes/engine/facebook/api.py b/package/src/org/maemo/hermes/engine/facebook/api.py new file mode 100644 index 0000000..19d57ed --- /dev/null +++ b/package/src/org/maemo/hermes/engine/facebook/api.py @@ -0,0 +1,64 @@ +import urllib, urllib2 +import simplejson + +class FacebookApi(): + """Facebook backend for Hermes, using the Graph API: + + http://developers.facebook.com/docs/reference/api/ + + Copyright (c) Andrew Flegg 2010. + Released under the Artistic Licence.""" + + + # ----------------------------------------------------------------------- + def __init__(self, oauth): + self._oauth = oauth + + + # ----------------------------------------------------------------------- + def authenticate(self): + '''Authenticate the user with Facebook.''' + + self._oauth.authorise('https://graph.facebook.com/oauth/authorize', + 'https://graph.facebook.com/oauth/access_token', + {'scope': 'user_about_me,friends_about_me,user_birthday,friends_birthday,user_website,friends_website,user_work_history,friends_work_history'}) + + + # ----------------------------------------------------------------------- + def get_user(self): + '''Return the name of the authenticated user.''' + + data = self._request('https://graph.facebook.com/me') + return data['name'] + + + # ----------------------------------------------------------------------- + def get_friends(self): + '''Return the full list of people being followed by the user. + + The result is a list of users: + http://developers.facebook.com/docs/reference/api/user/''' + + def copy(data, from_key, to, to_key = None): + if not to_key: + to_key = from_key + + if from_key in data: + to[to_key] = data[from_key] + + users = self._request('https://graph.facebook.com/me/friends', {'fields': 'id,name,link,birthday,website,picture', 'type': 'large'}) + return users['data'] + + + # ----------------------------------------------------------------------- + def _request(self, url, args = None): + """Make an authenticated request to Facebook and check the + JSON response. Return the dictionary if no errors.""" + + json = self._oauth.request(url, args) +# print json + data = simplejson.loads(json) + if 'error' in data: + raise Exception(data['error']) + + return data diff --git a/package/src/org/maemo/hermes/engine/facebook/provider.py b/package/src/org/maemo/hermes/engine/facebook/provider.py index 375db39..e693795 100644 --- a/package/src/org/maemo/hermes/engine/facebook/provider.py +++ b/package/src/org/maemo/hermes/engine/facebook/provider.py @@ -1,8 +1,9 @@ -from pythonfacebook import Facebook, FacebookError import gnome.gconf import gtk, hildon import org.maemo.hermes.engine.provider -import org.maemo.hermes.engine.facebook.service +from org.maemo.hermes.engine.facebook.service import Service +from org.maemo.hermes.engine.facebook.api import FacebookApi +import oauth2 class Provider(org.maemo.hermes.engine.provider.Provider): """Facebook provider for Hermes. @@ -20,18 +21,14 @@ class Provider(org.maemo.hermes.engine.provider.Provider): self._gc = gnome.gconf.client_get_default() - key_app = self._gc.get_string('/apps/maemo/hermes/facebook_app') + key_app = self._gc.get_string('/apps/maemo/hermes/facebook_key') key_secret = self._gc.get_string('/apps/maemo/hermes/facebook_secret') if key_app is None or key_secret is None: raise Exception('No Facebook application keys found. Installation error.') - self.fb = Facebook(key_app, key_secret) - self.fb.desktop = True - - if self.fb.session_key is None: - self.fb.session_key = self._gc.get_string('/apps/maemo/hermes/facebook_session_key') - self.fb.secret = self._gc.get_string('/apps/maemo/hermes/facebook_secret_key') - self.fb.uid = self._gc.get_string('/apps/maemo/hermes/facebook_uid') + access_token = self._gc.get_string('/apps/maemo/hermes/facebook_access_token') + self.oauth = oauth2.OAuth2(key_app, key_secret, access_token) + self.api = FacebookApi(self.oauth) # ----------------------------------------------------------------------- @@ -46,8 +43,7 @@ class Provider(org.maemo.hermes.engine.provider.Provider): def get_account_detail(self): """Return the email address associated with the user, if available.""" - name = self._gc.get_string('/apps/maemo/hermes/facebook_user') - return name and name or _('Pending authorisation') + return self._gc.get_string('/apps/maemo/hermes/facebook_user') # ----------------------------------------------------------------------- @@ -64,19 +60,19 @@ class Provider(org.maemo.hermes.engine.provider.Provider): dialog = gtk.Dialog(self.get_name(), parent) dialog.add_button(_('Disable'), gtk.RESPONSE_NO) - dialog.add_button(_('Enable'), gtk.RESPONSE_YES) + enable = dialog.add_button(_('Enable'), gtk.RESPONSE_YES) + + button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, + hildon.BUTTON_ARRANGEMENT_VERTICAL) + self._handle_button(None, button, enable) + button.connect('clicked', self._handle_button, button, enable) + dialog.vbox.add(button) checkbox = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT) checkbox.set_label(_('Create birthday-only contacts')) checkbox.set_active(self._gc.get_bool('/apps/maemo/hermes/facebook_birthday_only')) dialog.vbox.add(checkbox) - dialog.vbox.add(gtk.Label("\n" + _('Note: authentication via web page') + "\n")) - - clear = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, - hildon.BUTTON_ARRANGEMENT_VERTICAL, - title = _("Clear authorisation")) - clear.connect('clicked', self._clear_auth) - dialog.vbox.add(clear) + dialog.vbox.add(gtk.Label("")) dialog.show_all() result = dialog.run() @@ -89,14 +85,22 @@ class Provider(org.maemo.hermes.engine.provider.Provider): # ----------------------------------------------------------------------- - def _clear_auth(self, event): - """Clear Facebook authorisation information. Triggered by pressing - the 'clear' button in the preferences dialogue.""" + def _handle_button(self, e, button, enable): + """Ensure the button state is correct.""" + + authenticated = self._gc.get_string('/apps/maemo/hermes/facebook_access_token') is not None + if e is not None: + if authenticated: + self._gc.unset('/apps/maemo/hermes/facebook_access_token') + else: + self.api.authenticate() + self._gc.set_string('/apps/maemo/hermes/facebook_access_token', self.oauth.get_access_token()) + self._gc.set_string('/apps/maemo/hermes/facebook_user', self.api.get_user()) - self._gc.unset('/apps/maemo/hermes/facebook_session_key') - self._gc.unset('/apps/maemo/hermes/facebook_secret_key') - self._gc.unset('/apps/maemo/hermes/facebook_uid') - self._gc.unset('/apps/maemo/hermes/facebook_user') + authenticated = self._gc.get_string('/apps/maemo/hermes/facebook_access_token') is not None + + button.set_title(authenticated and _("Clear authorisation") or _("Authorise")) + enable.set_sensitive(authenticated) # ----------------------------------------------------------------------- @@ -104,44 +108,4 @@ class Provider(org.maemo.hermes.engine.provider.Provider): """Return a service instance.""" self._gui = gui_callback - - # Check the available session is still valid... - while True: - try: - if self.fb.users.getLoggedInUser() and self.fb.session_key: - break - except FacebookError: - pass - self._do_fb_login() - - return org.maemo.hermes.engine.facebook.service.Service(self.get_id(), self.fb, self._gc.get_bool('/apps/maemo/hermes/facebook_birthday_only')) - - - # ----------------------------------------------------------------------- - def _do_fb_login(self): - """Perform authentication against Facebook and store the result in gconf - for later use. Uses the 'need_auth' and 'block_for_auth' methods on - the callback class. The former allows a message to warn the user - about what is about to happen to be shown; the second is to wait - for the user to confirm they have logged in.""" - self.fb.session_key = None - self.fb.secret = None - self.fb.uid = None - - if self._gui: - self._gui.need_auth() - - self.fb.auth.createToken() - self.fb.login() - - if self._gui: - self._gui.block_for_auth() - - session = self.fb.auth.getSession() - self._gc.set_string('/apps/maemo/hermes/facebook_session_key', session['session_key']) - self._gc.set_string('/apps/maemo/hermes/facebook_secret_key', session['secret']) - self._gc.set_string('/apps/maemo/hermes/facebook_uid', str(session['uid'])) - - info = self.fb.users.getInfo([self.fb.uid], ['name']) - self._gc.set_string('/apps/maemo/hermes/facebook_user', info[0]['name']) - + return Service(self.get_id(), self.api, self._gc.get_bool('/apps/maemo/hermes/facebook_birthday_only')) diff --git a/package/src/org/maemo/hermes/engine/facebook/service.py b/package/src/org/maemo/hermes/engine/facebook/service.py index 0db44ca..ff46c26 100644 --- a/package/src/org/maemo/hermes/engine/facebook/service.py +++ b/package/src/org/maemo/hermes/engine/facebook/service.py @@ -32,7 +32,8 @@ class Service(org.maemo.hermes.engine.service.Service): friends = [] if self._create_birthday_only: for friend in self._friends_without_contact: - friends.append(friend) + if friend.is_interesting(): + friends.append(friend) return friends @@ -77,20 +78,20 @@ class Service(org.maemo.hermes.engine.service.Service): if key in data and data[key]: callback(data[key]) - friends_data = self._get_friends_data() + friends_data = self.fb.get_friends() for data in friends_data: friend = Friend(data['name']) - if 'profile_url' not in data: - data['profile_url'] = "http://www.facebook.com/profile.php?id=" + str(data['uid']) + if 'link' not in data: + data['link'] = "http://www.facebook.com/profile.php?id=" + str(data['id']) if_defined(data, 'website', friend.add_url) - if_defined(data, 'profile_url', friend.add_url) - if_defined(data, 'birthday_date', friend.set_birthday_date) + if_defined(data, 'link', friend.add_url) + if_defined(data, 'birthday', friend.set_birthday_date) - if_defined(data, 'pic_big', friend.set_photo_url) + if_defined(data, 'picture', friend.set_photo_url) - url = data['profile_url'] + url = data['link'] friend.add_url(url) self._register_friend(friend) @@ -157,11 +158,3 @@ class Service(org.maemo.hermes.engine.service.Service): self._friends_without_contact.discard(friend) self._friends_by_contact[contact] = friend self._contacts_by_friend[friend] = contact - - - # ----------------------------------------------------------------------- - def _get_friends_data(self): - """Returns a list of dicts, where each dict represents the raw data - of a friend""" - - return self.fb.users.getInfo(self.fb.friends.get(), Service.attrs) diff --git a/package/test/automatic_tests.py b/package/test/automatic_tests.py index 0e26884..5f7ee62 100644 --- a/package/test/automatic_tests.py +++ b/package/test/automatic_tests.py @@ -2,7 +2,7 @@ import unittest -from unit.test_facebook import TestFacebookService +from unit.test_facebook import TestFacebookService, TestFacebookAPI from unit.test_gravatar import TestGravatarService from unit.test_linkedin import TestLinkedInService from unit.test_twitter import TestTwitterService @@ -13,6 +13,9 @@ from unit.test_phonenumber import TestPhoneNumber from integration.test_gravatar import IntegrationTestGravatarService from integration.test_linkedinapi import IntegrationTestLinkedInApi from integration.test_twitter import IntegrationTestTwitterService +from integration.test_oauth2 import IntegrationTestOAuth2 +from integration.test_facebook import IntegrationTestFacebook + if __name__ == '__main__': unittest.main() diff --git a/package/test/integration/test_facebook.py b/package/test/integration/test_facebook.py new file mode 100644 index 0000000..ca2973b --- /dev/null +++ b/package/test/integration/test_facebook.py @@ -0,0 +1,36 @@ +import unittest +import oauth2 +from org.maemo.hermes.engine.facebook.api import FacebookApi +import httplib +httplib.HTTPConnection.debuglevel = 1 + +class IntegrationTestFacebook(unittest.TestCase): + access_token = None + + # ----------------------------------------------------------------------- + def setUp(self): + self.oauth = oauth2.OAuth2('5916f12942feea4b3247d42a84371112', '19f7538edd96b6870f2da7e84a6390a4', IntegrationTestFacebook.access_token) + self.facebook = FacebookApi(self.oauth) + + # ----------------------------------------------------------------------- + def test_authenticate(self): + self.facebook.authenticate() + IntegrationTestFacebook.access_token = self.oauth.get_access_token() + + + # ----------------------------------------------------------------------- + def test_get_user(self): + user = self.facebook.get_user() + print user + assert user + + + # ----------------------------------------------------------------------- + def test_get_friends(self): + friends = self.facebook.get_friends() + print friends + assert friends + + +if __name__ == '__main__': + unittest.main() diff --git a/package/test/integration/test_oauth2.py b/package/test/integration/test_oauth2.py new file mode 100644 index 0000000..f2f6c0d --- /dev/null +++ b/package/test/integration/test_oauth2.py @@ -0,0 +1,41 @@ +import unittest +import oauth2 +import httplib +import simplejson +httplib.HTTPConnection.debuglevel = 1 + +class IntegrationTestOAuth2(unittest.TestCase): + access_token = None + + # ----------------------------------------------------------------------- + def setUp(self): + self.oauth = oauth2.OAuth2('5916f12942feea4b3247d42a84371112', '19f7538edd96b6870f2da7e84a6390a4', IntegrationTestOAuth2.access_token) + + + # ----------------------------------------------------------------------- + def test_authorisation(self): + self.oauth.authorise('https://graph.facebook.com/oauth/authorize', + 'https://graph.facebook.com/oauth/access_token', + {'scope': 'user_about_me,friends_about_me,user_birthday,friends_birthday,user_website,friends_website,user_work_history,friends_work_history'}) + + IntegrationTestOAuth2.access_token = self.oauth.get_access_token() + assert IntegrationTestOAuth2.access_token + + + # ----------------------------------------------------------------------- + def test_request(self): + if not IntegrationTestOAuth2.access_token: + self.test_authorisation() + + response = self.oauth.request('https://graph.facebook.com/me') + data = simplejson.loads(response) + if 'error' in data: + print response + raise Exception(data['error']) + + print response + assert 'name' in data and data['name'] + + +if __name__ == '__main__': + unittest.main() diff --git a/package/test/unit/test_facebook.py b/package/test/unit/test_facebook.py index b766d2f..2b3e958 100644 --- a/package/test/unit/test_facebook.py +++ b/package/test/unit/test_facebook.py @@ -1,4 +1,5 @@ from org.maemo.hermes.engine.facebook.service import Service +from org.maemo.hermes.engine.facebook.api import FacebookApi from org.maemo.hermes.engine.names import canonical from org.maemo.hermes.engine.friend import Friend import unittest @@ -18,38 +19,58 @@ class FakeContact(): def get_identifiers(self): return [canonical(self.name)] +class FakeFacebookApi(): + friends = [] + + def get_friends(self): + return FakeFacebookApi.friends + +class FakeOAuth(): + def authorise(self, authorise_url, access_token_url, args = None): + pass + + def request(self, url, args = None): + if url == 'https://graph.facebook.com/me': + return '{"id":"1234567","name":"Maemo Hermes"}' + + elif url == 'https://graph.facebook.com/me/friends': + return '{"data":[{"id":"1","name":"J Smith","website":"http:\/\/www.example.org"},{"id":"2","name":"P Barnum"}]}' + + else: + raise Exception("Unknown URL: %s" % (url)) + class TestFacebookService(unittest.TestCase): def setUp(self): - self.testee = Service("facebook", None) + self.testee = Service("facebook", FakeFacebookApi()) def test_that_get_friends_to_create_contacts_for_works(self): def run_test(expected_length): - self._fake_server_response([{'uid':'123456','name':'Facebook Junkie', 'birthday_date':'now'}]) + self._fake_server_response([{'id':'123456','name':'Facebook Junkie', 'birthday':'now'}]) self._run_service([]) friends = self.testee.get_friends_to_create_contacts_for() assert len(friends) == expected_length # default is to NOT create contacts - self.testee = Service("facebook", None) + self.testee = Service("facebook", FakeFacebookApi()) run_test(0) # passing False explicitly - self.testee = Service("facebook", None, False) + self.testee = Service("facebook", FakeFacebookApi(), False) run_test(0) # passing True to constructor - self.testee = Service("facebook", None, True) + self.testee = Service("facebook", FakeFacebookApi(), True) run_test(1) def test_that_gftccf_returns_friends_with_birth_date(self): - self.testee = Service("facebook", None, True) + self.testee = Service("facebook", FakeFacebookApi(), True) bday = '1980-10-15' - props_with_bday = {'uid':'123456','name':'Facebook Junkie', 'birthday_date':bday} - props_without = {'uid':'123457','name':'Johnny Secret'} + props_with_bday = {'id':'123456','name':'Facebook Junkie', 'birthday':bday} + props_without = {'id':'123457','name':'Johnny Secret'} self._fake_server_response([props_with_bday, props_without]) self._run_service([]) @@ -63,7 +84,7 @@ class TestFacebookService(unittest.TestCase): def test_that_process_contact_returns_friend_object_for_known_contact(self): known_url = 'http://www.facebook.com/profile.php?id=123456' known_contact = FakeContact('Facebook Junkie', [known_url]) - self._fake_server_response([{'uid':'123456','name':'Facebook Junkie'}]) + self._fake_server_response([{'id':'123456','name':'Facebook Junkie'}]) self.testee.process_friends() result = self.testee.process_contact(known_contact) @@ -83,11 +104,11 @@ class TestFacebookService(unittest.TestCase): # arrange self.existing_address = 'http://www.facebook.com/profile.php?id=123456' self.existing_contact = FakeContact("Facebook Person", [self.existing_address]) - existing_fake = {'uid':'123456','name':'Name Doesnt Match but URL Does'} + existing_fake = {'id':'123456','name':'Name Doesnt Match but URL Does'} self.other_address = 'http://twitter.com/not/correct/site' self.other_contact = FakeContact("Twitter Person", [self.other_address]) - other_fake = {'uid':'123','name':self.other_contact.get_name()} + other_fake = {'id':'123','name':self.other_contact.get_name()} self.none_contact = FakeContact("No URLson", []) @@ -114,7 +135,7 @@ class TestFacebookService(unittest.TestCase): contact_do_match = FakeContact(name, ["http://www.facebook.com/profile.php?id=123"], 1); contact_no_match = FakeContact(name, [None], 2) - data = [{'uid':'123','name':name}] + data = [{'id':'123','name':name}] self._fake_server_response(data) self._run_service([contact_no_match, contact_do_match]) @@ -130,7 +151,7 @@ class TestFacebookService(unittest.TestCase): contact_do_match = FakeContact(name, ["Contact 1"]); contact_no_match = FakeContact(name, ["Contact 2"]) - data = [{'uid':'123','name':name}] + data = [{'id':'123','name':name}] self._fake_server_response(data) self._run_service([contact_no_match, contact_do_match]) @@ -148,12 +169,28 @@ class TestFacebookService(unittest.TestCase): self.testee.process_contact(contact) def _fake_server_response(self, data): - self.testee._get_friends_data = self._get_friends_data - self._server_response = data - - def _get_friends_data(self): - return self._server_response + FakeFacebookApi.friends = data + +class TestFacebookAPI(unittest.TestCase): + + def setUp(self): + self.oauth = FakeOAuth() + self.api = FacebookApi(self.oauth) + + def test_authenticate(self): + pass + + + def test_get_user(self): + assert self.api.get_user() == 'Maemo Hermes' + + + def test_get_friends(self): + friends = self.api.get_friends() + assert len(friends) == 2 + assert friends[0]['name'] == 'J Smith' + assert friends[1]['id'] == '2' if __name__ == '__main__': -- 1.7.9.5