Redistribute responsibility: Let contact instances check if features are supported

This commit is contained in:
Stephan Erb 2009-10-27 20:31:09 +01:00
parent 3295b08b26
commit c7ff97703f
5 changed files with 188 additions and 262 deletions

View File

@ -39,51 +39,30 @@ import gajim
import helpers import helpers
from common.xmpp import NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, NS_CHATSTATES from common.xmpp import NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION, NS_CHATSTATES
# Features where we cannot safely assume that the other side supports them # Features where we cannot safely assume that the other side supports them
FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION] FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_RECEIPTS, NS_ESESSION]
class AbstractClientCaps(object): class AbstractClientCaps(object):
''' '''
Base class representing a client and its capabilities as advertised by Base class representing a client and its capabilities as advertised by
a caps tag in a presence a caps tag in a presence
''' '''
def __init__(self, caps_cache, caps_hash, node): def __init__(self, caps_hash, node):
self._caps_cache = caps_cache
self._hash = caps_hash self._hash = caps_hash
self._node = node self._node = node
def query_client_of_jid_if_unknown(self, connection, jid): def get_discover_strategy(self):
''' return self._discover
Asynchronously query the give jid for its (node, ver, exts) caps data
using a disco query.
Query will only be sent if the data is not already cached.
'''
q = self._lookup_in_cache()
if q.queried == 0:
self._discover(connection, jid)
q.queried = 1
def contains_feature(self, requested_feature):
''' Returns true if these capabilities contain the given feature '''
cach_entry = self._lookup_in_cache()
supported_features = cach_entry.features
if requested_feature in supported_features:
return True
elif supported_features == [] and cach_entry.queried in (0, 1):
# assume feature is supported, if not blacklisted
return requested_feature not in FEATURE_BLACKLIST
else:
return False
def _discover(self, connection, jid): def _discover(self, connection, jid):
''' To be implemented by subclassess ''' ''' To be implemented by subclassess '''
raise NotImplementedError() raise NotImplementedError()
def _lookup_in_cache(self): def get_cache_lookup_strategy(self):
return self._lookup_in_cache
def _lookup_in_cache(self, caps_cache):
''' To be implemented by subclassess ''' ''' To be implemented by subclassess '''
raise NotImplementedError() raise NotImplementedError()
@ -91,13 +70,13 @@ class AbstractClientCaps(object):
class ClientCaps(AbstractClientCaps): class ClientCaps(AbstractClientCaps):
''' The current XEP-115 implementation ''' ''' The current XEP-115 implementation '''
def __init__(self, caps_cache, caps_hash, node, hash_method): def __init__(self, caps_hash, node, hash_method):
AbstractClientCaps.__init__(self, caps_cache, caps_hash, node) AbstractClientCaps.__init__(self, caps_hash, node)
assert hash_method != 'old' assert hash_method != 'old'
self._hash_method = hash_method self._hash_method = hash_method
def _lookup_in_cache(self): def _lookup_in_cache(self, caps_cache):
return self._caps_cache[(self._hash_method, self._hash)] return caps_cache[(self._hash_method, self._hash)]
def _discover(self, connection, jid): def _discover(self, connection, jid):
connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash)) connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash))
@ -106,11 +85,11 @@ class ClientCaps(AbstractClientCaps):
class OldClientCaps(AbstractClientCaps): class OldClientCaps(AbstractClientCaps):
''' Old XEP-115 implemtation. Kept around for background competability. ''' ''' Old XEP-115 implemtation. Kept around for background competability. '''
def __init__(self, caps_cache, caps_hash, node): def __init__(self, caps_hash, node):
AbstractClientCaps.__init__(self, caps_cache, caps_hash, node) AbstractClientCaps.__init__(self, caps_hash, node)
def _lookup_in_cache(self): def _lookup_in_cache(self, caps_cache):
return self._caps_cache[('old', self._node + '#' + self._hash)] return caps_cache[('old', self._node + '#' + self._hash)]
def _discover(self, connection, jid): def _discover(self, connection, jid):
connection.discoverInfo(jid) connection.discoverInfo(jid)
@ -125,32 +104,19 @@ class NullClientCaps(AbstractClientCaps):
''' '''
def __init__(self): def __init__(self):
pass AbstractClientCaps.__init__(self, None, None)
def query_client_of_jid_if_unknown(self, connection, jid): def _lookup_in_cache(self, caps_cache):
pass return caps_cache[('old', '')]
def contains_feature(self, feature): def _discover(self, connection, jid):
return feature not in FEATURE_BLACKLIST pass
class CapsCache(object): class CapsCache(object):
''' This object keeps the mapping between caps data and real disco '''
This object keeps the mapping between caps data and real disco
features they represent, and provides simple way to query that info. features they represent, and provides simple way to query that info.
It is application-wide, that is there's one object for all
connections.
Goals:
* handle storing/retrieving info from database
* cache info in memory
* expose simple interface
Properties:
* one object for all connections (move to logger.py?)
* store info efficiently (a set() of urls -- we can assume there won't be
too much of these, ensure that (X,Y,Z1) and (X,Y,Z2) has different
features.
''' '''
def __init__(self, logger=None): def __init__(self, logger=None):
''' Create a cache for entity capabilities. ''' ''' Create a cache for entity capabilities. '''
@ -219,15 +185,14 @@ class CapsCache(object):
def update(self, identities, features): def update(self, identities, features):
# NOTE: self refers to CapsCache object, not to CacheItem # NOTE: self refers to CapsCache object, not to CacheItem
self.identities=identities self.identities = identities
self.features=features self.features = features
self._logger.add_caps_entry(self.hash_method, self.hash, self._logger.add_caps_entry(self.hash_method, self.hash,
identities, features) identities, features)
self.__CacheItem = CacheItem self.__CacheItem = CacheItem
# prepopulate data which we are sure of; note: we do not log these info # prepopulate data which we are sure of; note: we do not log these info
for account in gajim.connections: for account in gajim.connections:
gajimcaps = self[('sha-1', gajim.caps_hash[account])] gajimcaps = self[('sha-1', gajim.caps_hash[account])]
gajimcaps.identities = [gajim.gajim_identity] gajimcaps.identities = [gajim.gajim_identity]
@ -257,48 +222,19 @@ class CapsCache(object):
self.__cache[(hash_method, hash_)] = x self.__cache[(hash_method, hash_)] = x
return x return x
def preload(self, con, jid, node, hash_method, hash_): def query_client_of_jid_if_unknown(self, connection, jid, client_caps):
''' Preload data about (node, ver, exts) caps using disco ''' Preload data about (node, ver, exts) caps using disco
query to jid using proper connection. Don't query if query to jid using proper connection. Don't query if
the data is already in cache. ''' the data is already in cache. '''
if hash_method == 'old': lookup_cache_item = client_caps.get_cache_lookup_strategy()
q = self[(hash_method, node + '#' + hash_)] q = lookup_cache_item(self)
else:
q = self[(hash_method, hash_)]
if q.queried==0: if q.queried == 0:
# do query for bare node+hash pair # do query for bare node+hash pair
# this will create proper object # this will create proper object
q.queried=1 q.queried = 1
if hash_method == 'old': discover = client_caps.get_discover_strategy()
con.discoverInfo(jid) discover(connection, jid)
else:
con.discoverInfo(jid, '%s#%s' % (node, hash_))
def is_supported(self, contact, feature):
if not contact:
return False
# Unfortunately, if all resources are offline, the contact
# includes the last resource that was online. Check for its
# show, so we can be sure it's existant. Otherwise, we still
# return caps for a contact that has no resources left.
if contact.show == 'offline':
return False
# FIXME: We assume everything is supported if we got no caps.
# This is the "Asterix way", after 0.12 release, I will
# likely implement a fallback to disco (could be disabled
# for mobile users who pay for traffic)
if contact.caps_hash_method == 'old':
features = self[(contact.caps_hash_method, contact.caps_node + '#' + \
contact.caps_hash)].features
else:
features = self[(contact.caps_hash_method, contact.caps_hash)].features
if feature in features or features == []:
return True
return False
gajim.capscache = CapsCache(gajim.logger) gajim.capscache = CapsCache(gajim.logger)

View File

@ -30,12 +30,15 @@
import common.gajim import common.gajim
from common.caps import NullClientCaps, FEATURE_BLACKLIST
class Contact(object): class Contact(object):
'''Information concerning each contact''' '''Information concerning each contact'''
def __init__(self, jid='', name='', groups=[], show='', status='', sub='', def __init__(self, jid='', name='', groups=[], show='', status='', sub='',
ask='', resource='', priority=0, keyID='', caps_node=None, ask='', resource='', priority=0, keyID='', client_caps=None, caps_cache=None,
caps_hash_method=None, caps_hash=None, our_chatstate=None, chatstate=None, our_chatstate=None, chatstate=None, last_status_time=None, msg_id = None,
last_status_time=None, msg_id = None, composing_xep = None, mood={}, tune={}, composing_xep = None, mood={}, tune={},
activity={}): activity={}):
self.jid = jid self.jid = jid
self.name = name self.name = name
@ -49,11 +52,9 @@ class Contact(object):
self.priority = priority self.priority = priority
self.keyID = keyID self.keyID = keyID
# Capabilities; filled by caps.py/ConnectionCaps object # Entity Capabilities
# every time it gets these from presence stanzas self._client_caps = client_caps or NullClientCaps()
self.caps_node = caps_node self._caps_cache = caps_cache or common.gajim.capscache
self.caps_hash_method = caps_hash_method
self.caps_hash = caps_hash
# please read xep-85 http://www.xmpp.org/extensions/xep-0085.html # please read xep-85 http://www.xmpp.org/extensions/xep-0085.html
# we keep track of xep85 support with the peer by three extra states: # we keep track of xep85 support with the peer by three extra states:
@ -135,20 +136,7 @@ class Contact(object):
return False return False
def _set_supported_caps(self, value): def supports(self, requested_feature):
'''
Set an EntityCapabilities object
'''
self._caps = value
def _get_supported_caps(self):
'''
Returns a function which delegates to the EntityCapabilites support checker
This allows easy checks like:
if contact.supports(NS_COOL_FEATURE): ...
'''
def test(feature):
if self.show == 'offline': if self.show == 'offline':
# Unfortunately, if all resources are offline, the contact # Unfortunately, if all resources are offline, the contact
# includes the last resource that was online. Check for its # includes the last resource that was online. Check for its
@ -156,10 +144,25 @@ class Contact(object):
# return caps for a contact that has no resources left. # return caps for a contact that has no resources left.
return False return False
else: else:
return self._caps.contains_feature(feature) return self._client_supports(requested_feature)
return test
supports = property(_get_supported_caps, _set_supported_caps) def _client_supports(self, requested_feature):
lookup_item = self._client_caps.get_cache_lookup_strategy()
cache_item = lookup_item(self._caps_cache)
supported_features = cache_item.features
if requested_feature in supported_features:
return True
elif supported_features == [] and cache_item.queried in (0, 1):
# assume feature is supported, if we don't know yet, what the client
# is capable of
return requested_feature not in FEATURE_BLACKLIST
else:
return False
def set_supported_client_caps(self, client_caps):
''' Set an EntityCapabilities object '''
self._client_caps = client_caps
class GC_Contact: class GC_Contact:
@ -175,9 +178,11 @@ class GC_Contact:
self.affiliation = affiliation self.affiliation = affiliation
self.jid = jid self.jid = jid
self.resource = resource self.resource = resource
self.caps_node = None
self.caps_hash_method = None # Entity Capabilities
self.caps_hash = None self._client_caps = NullClientCaps()
self._caps_cache = common.gajim.capscache
self.our_chatstate = our_chatstate self.our_chatstate = our_chatstate
self.composing_xep = composing_xep self.composing_xep = composing_xep
self.chatstate = chatstate self.chatstate = chatstate
@ -188,20 +193,7 @@ class GC_Contact:
def get_shown_name(self): def get_shown_name(self):
return self.name return self.name
def _set_supported_caps(self, value): def supports(self, requested_feature):
'''
Set an EntityCapabilities object
'''
self._caps = value
def _get_supported_caps(self):
'''
Returns a function which delegates to the EntityCapabilites support checker
This allows easy checks like:
if contact.supports(NS_COOL_FEATURE): ...
'''
def test(feature):
if self.show == 'offline': if self.show == 'offline':
# Unfortunately, if all resources are offline, the contact # Unfortunately, if all resources are offline, the contact
# includes the last resource that was online. Check for its # includes the last resource that was online. Check for its
@ -209,10 +201,25 @@ class GC_Contact:
# return caps for a contact that has no resources left. # return caps for a contact that has no resources left.
return False return False
else: else:
return self._caps.contains_feature(feature) return self._client_supports(requested_feature)
return test
supports = property(_get_supported_caps, _set_supported_caps) def _client_supports(self, requested_feature):
lookup_item = self._client_caps.get_cache_lookup_strategy()
cache_item = lookup_item(self._caps_cache)
supported_features = cache_item.features
if requested_feature in supported_features:
return True
elif supported_features == [] and cache_item.queried in (0, 1):
# assume feature is supported, if we don't know yet, what the client
# is capable of
return requested_feature not in FEATURE_BLACKLIST
else:
return False
def set_supported_client_caps(self, client_caps):
''' Set an EntityCapabilities object '''
self._client_caps = client_caps
class Contacts: class Contacts:
'''Information concerning all contacts and groupchat contacts''' '''Information concerning all contacts and groupchat contacts'''
@ -246,8 +253,8 @@ class Contacts:
del self._metacontacts_tags[account] del self._metacontacts_tags[account]
def create_contact(self, jid='', name='', groups=[], show='', status='', def create_contact(self, jid='', name='', groups=[], show='', status='',
sub='', ask='', resource='', priority=0, keyID='', caps_node=None, sub='', ask='', resource='', priority=0, keyID='', client_caps=None,
caps_hash_method=None, caps_hash=None, our_chatstate=None, caps_cache=None, our_chatstate=None,
chatstate=None, last_status_time=None, composing_xep=None, chatstate=None, last_status_time=None, composing_xep=None,
mood={}, tune={}, activity={}): mood={}, tune={}, activity={}):
@ -259,8 +266,8 @@ class Contacts:
return Contact(jid=jid, name=name, groups=groups_unique, show=show, return Contact(jid=jid, name=name, groups=groups_unique, show=show,
status=status, sub=sub, ask=ask, resource=resource, priority=priority, status=status, sub=sub, ask=ask, resource=resource, priority=priority,
keyID=keyID, caps_node=caps_node, caps_hash_method=caps_hash_method, keyID=keyID, client_caps=client_caps, caps_cache=caps_cache,
caps_hash=caps_hash, our_chatstate=our_chatstate, chatstate=chatstate, our_chatstate=our_chatstate, chatstate=chatstate,
last_status_time=last_status_time, composing_xep=composing_xep, last_status_time=last_status_time, composing_xep=composing_xep,
mood=mood, tune=tune, activity=activity) mood=mood, tune=tune, activity=activity)
@ -269,8 +276,8 @@ class Contacts:
groups=contact.groups, show=contact.show, status=contact.status, groups=contact.groups, show=contact.show, status=contact.status,
sub=contact.sub, ask=contact.ask, resource=contact.resource, sub=contact.sub, ask=contact.ask, resource=contact.resource,
priority=contact.priority, keyID=contact.keyID, priority=contact.priority, keyID=contact.keyID,
caps_node=contact.caps_node, caps_hash_method=contact.caps_hash_method, client_caps=contact._client_caps, caps_cache=contact._caps_cache,
caps_hash=contact.caps_hash, our_chatstate=contact.our_chatstate, our_chatstate=contact.our_chatstate,
chatstate=contact.chatstate, last_status_time=contact.last_status_time) chatstate=contact.chatstate, last_status_time=contact.last_status_time)
def add_contact(self, account, contact): def add_contact(self, account, contact):
@ -640,9 +647,8 @@ class Contacts:
jid = gc_contact.get_full_jid() jid = gc_contact.get_full_jid()
return Contact(jid=jid, resource=gc_contact.resource, return Contact(jid=jid, resource=gc_contact.resource,
name=gc_contact.name, groups=[], show=gc_contact.show, name=gc_contact.name, groups=[], show=gc_contact.show,
status=gc_contact.status, sub='none', caps_node=gc_contact.caps_node, status=gc_contact.status, sub='none', client_caps=gc_contact._client_caps,
caps_hash_method=gc_contact.caps_hash_method, caps_cache=gc_contact._caps_cache)
caps_hash=gc_contact.caps_hash)
def create_gc_contact(self, room_jid='', name='', show='', status='', def create_gc_contact(self, room_jid='', name='', show='', status='',
role='', affiliation='', jid='', resource=''): role='', affiliation='', jid='', resource=''):

View File

@ -32,8 +32,6 @@ import logging
import locale import locale
import config import config
from contacts import Contacts
from events import Events
import xmpp import xmpp
try: try:
@ -101,6 +99,9 @@ else:
os_info = None # used to cache os information os_info = None # used to cache os information
from contacts import Contacts
from events import Events
gmail_domains = ['gmail.com', 'googlemail.com'] gmail_domains = ['gmail.com', 'googlemail.com']
transport_type = {} # list the type of transport transport_type = {} # list the type of transport

View File

@ -6,11 +6,11 @@ import unittest
import lib import lib
lib.setup_env() lib.setup_env()
from common import gajim
from common import helpers from common import helpers
from common.contacts import Contact
from common.xmpp import NS_MUC, NS_PING, NS_XHTML_IM from common.xmpp import NS_MUC, NS_PING, NS_XHTML_IM
from common.caps import CapsCache, ClientCaps, OldClientCaps from common.caps import CapsCache, ClientCaps, OldClientCaps
from common.contacts import Contact
from mock import Mock from mock import Mock
@ -20,7 +20,7 @@ class CommonCapsTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.caps_method = 'sha-1' self.caps_method = 'sha-1'
self.caps_hash = 'm3P2WeXPMGVH2tZPe7yITnfY0Dw=' self.caps_hash = 'm3P2WeXPMGVH2tZPe7yITnfY0Dw='
self.caps = (self.caps_method, self.caps_hash) self.client_caps = (self.caps_method, self.caps_hash)
self.node = "http://gajim.org" self.node = "http://gajim.org"
self.identity = {'category': 'client', 'type': 'pc', 'name':'Gajim'} self.identity = {'category': 'client', 'type': 'pc', 'name':'Gajim'}
@ -35,6 +35,10 @@ class CommonCapsTest(unittest.TestCase):
self.logger = Mock(returnValues={"iter_caps_data":db_caps_cache}) self.logger = Mock(returnValues={"iter_caps_data":db_caps_cache})
self.cc = CapsCache(self.logger) self.cc = CapsCache(self.logger)
# This is a temporary hack required by the way contacts rely on the
# existance of a cache. Hopefully this can be refactored to work via
# dependency injection
gajim.capscache = self.cc
class TestCapsCache(CommonCapsTest): class TestCapsCache(CommonCapsTest):
@ -42,13 +46,13 @@ class TestCapsCache(CommonCapsTest):
def test_set_retrieve(self): def test_set_retrieve(self):
''' Test basic set / retrieve cycle ''' ''' Test basic set / retrieve cycle '''
self.cc[self.caps].identities = self.identities self.cc[self.client_caps].identities = self.identities
self.cc[self.caps].features = self.features self.cc[self.client_caps].features = self.features
self.assert_(NS_MUC in self.cc[self.caps].features) self.assert_(NS_MUC in self.cc[self.client_caps].features)
self.assert_(NS_PING not in self.cc[self.caps].features) self.assert_(NS_PING not in self.cc[self.client_caps].features)
identities = self.cc[self.caps].identities identities = self.cc[self.client_caps].identities
self.assertEqual(1, len(identities)) self.assertEqual(1, len(identities))
@ -57,9 +61,9 @@ class TestCapsCache(CommonCapsTest):
self.assertEqual('pc', identity['type']) self.assertEqual('pc', identity['type'])
def test_update(self): def test_update(self):
''' Test caps update gets logged into db ''' ''' Test client_caps update gets logged into db '''
item = self.cc[self.caps] item = self.cc[self.client_caps]
item.update(self.identities, self.features) item.update(self.identities, self.features)
self.logger.mockCheckCall(0, "add_caps_entry", self.caps_method, self.logger.mockCheckCall(0, "add_caps_entry", self.caps_method,
@ -67,46 +71,31 @@ class TestCapsCache(CommonCapsTest):
def test_initialize_from_db(self): def test_initialize_from_db(self):
''' Read cashed dummy data from db ''' ''' Read cashed dummy data from db '''
self.assertEqual(self.cc[self.caps].queried, 0) self.assertEqual(self.cc[self.client_caps].queried, 0)
self.cc.initialize_from_db() self.cc.initialize_from_db()
self.assertEqual(self.cc[self.caps].queried, 2) self.assertEqual(self.cc[self.client_caps].queried, 2)
def test_preload_triggering_query(self): def test_preload_triggering_query(self):
''' Make sure that preload issues a disco ''' ''' Make sure that preload issues a disco '''
connection = Mock() connection = Mock()
client_caps = ClientCaps(self.caps_hash, self.node, self.caps_method)
self.cc.preload(connection, "test@gajim.org", self.node, self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org",
self.caps_method, self.caps_hash) client_caps)
connection.mockCheckCall(0, "discoverInfo", "test@gajim.org", self.assertEqual(1, len(connection.mockGetAllCalls()))
"http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=")
def test_no_preload_query_if_cashed(self): def test_no_preload_query_if_cashed(self):
''' Preload must not send a query if the data is already cached ''' ''' Preload must not send a query if the data is already cached '''
connection = Mock() connection = Mock()
client_caps = ClientCaps(self.caps_hash, self.node, self.caps_method)
self.cc.initialize_from_db() self.cc.initialize_from_db()
self.cc.preload(connection, "test@gajim.org", self.node, self.cc.query_client_of_jid_if_unknown(connection, "test@gajim.org",
self.caps_method, self.caps_hash) client_caps)
self.assertEqual(0, len(connection.mockGetAllCalls())) self.assertEqual(0, len(connection.mockGetAllCalls()))
def test_is_supported(self):
contact = Contact(caps_node=self.node,
caps_hash_method=self.caps_method,
caps_hash=self.caps_hash)
self.assertTrue(self.cc.is_supported(contact, NS_PING),
msg="Assume everything is supported, if we don't have caps")
self.cc.initialize_from_db()
self.assertFalse(self.cc.is_supported(contact, NS_PING),
msg="Must return false on unsupported feature")
self.assertTrue(self.cc.is_supported(contact, NS_MUC),
msg="Must return True on supported feature")
def test_hash(self): def test_hash(self):
'''tests the hash computation''' '''tests the hash computation'''
computed_hash = helpers.compute_caps_hash(self.identities, self.features) computed_hash = helpers.compute_caps_hash(self.identities, self.features)
@ -117,41 +106,36 @@ class TestClientCaps(CommonCapsTest):
def setUp(self): def setUp(self):
CommonCapsTest.setUp(self) CommonCapsTest.setUp(self)
self.caps = ClientCaps(self.cc, self.caps_hash, self.node, self.client_caps = ClientCaps(self.caps_hash, self.node, self.caps_method)
self.caps_method)
def test_no_query_client_of_jid(self): def test_query_by_get_discover_strategy(self):
''' Client must not be queried if the data is already cached '''
connection = Mock()
self.cc.initialize_from_db()
self.caps.query_client_of_jid_if_unknown(connection, "test@gajim.org")
self.assertEqual(0, len(connection.mockGetAllCalls()))
def test_query_client_of_jid_if_unknown(self):
''' Client must be queried if the data is unkown ''' ''' Client must be queried if the data is unkown '''
connection = Mock() connection = Mock()
self.caps.query_client_of_jid_if_unknown(connection, "test@gajim.org") discover = self.client_caps.get_discover_strategy()
discover(connection, "test@gajim.org")
connection.mockCheckCall(0, "discoverInfo", "test@gajim.org", connection.mockCheckCall(0, "discoverInfo", "test@gajim.org",
"http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=") "http://gajim.org#m3P2WeXPMGVH2tZPe7yITnfY0Dw=")
def test_is_supported(self): def test_client_supports(self):
self.assertTrue(self.caps.contains_feature(NS_PING), contact = Contact(caps_cache=self.cc)
contact.set_supported_client_caps(self.client_caps)
self.assertTrue(contact.supports(NS_PING),
msg="Assume supported, if we don't have caps") msg="Assume supported, if we don't have caps")
self.assertFalse(self.caps.contains_feature(NS_XHTML_IM), self.assertFalse(contact.supports(NS_XHTML_IM),
msg="Must not assume blacklisted feature is supported on default") msg="Must not assume blacklisted feature is supported on default")
self.cc.initialize_from_db() self.cc.initialize_from_db()
self.assertFalse(self.caps.contains_feature(NS_PING), self.assertFalse(contact.supports(NS_PING),
msg="Must return false on unsupported feature") msg="Must return false on unsupported feature")
self.assertTrue(self.caps.contains_feature(NS_XHTML_IM), self.assertTrue(contact.supports(NS_XHTML_IM),
msg="Must return True on supported feature") msg="Must return True on supported feature")
self.assertTrue(self.caps.contains_feature(NS_MUC), self.assertTrue(contact.supports(NS_MUC),
msg="Must return True on supported feature") msg="Must return True on supported feature")
@ -159,25 +143,17 @@ class TestOldClientCaps(TestClientCaps):
def setUp(self): def setUp(self):
TestClientCaps.setUp(self) TestClientCaps.setUp(self)
self.caps = OldClientCaps(self.cc, self.caps_hash, self.node) self.client_caps = OldClientCaps(self.caps_hash, self.node)
def test_no_query_client_of_jid(self): def test_query_by_get_discover_strategy(self):
''' Client must not be queried if the data is already cached ''' ''' Client must be queried if the data is unknown '''
connection = Mock() connection = Mock()
self.cc.initialize_from_db() discover = self.client_caps.get_discover_strategy()
self.caps.query_client_of_jid_if_unknown(connection, "test@gajim.org") discover(connection, "test@gajim.org")
self.assertEqual(0, len(connection.mockGetAllCalls()))
def test_query_client_of_jid_if_unknown(self):
''' Client must be queried if the data is unkown '''
connection = Mock()
self.caps.query_client_of_jid_if_unknown(connection, "test@gajim.org")
connection.mockCheckCall(0, "discoverInfo", "test@gajim.org") connection.mockCheckCall(0, "discoverInfo", "test@gajim.org")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -6,36 +6,43 @@ import unittest
import lib import lib
lib.setup_env() lib.setup_env()
from common.contacts import Contact from common.contacts import Contact, GC_Contact
from common.caps import NullClientCaps from common.caps import NullClientCaps
from common.xmpp import NS_MUC
from mock import Mock class TestCommonContact(unittest.TestCase):
class TestContact(unittest.TestCase): def setUp(self):
self.contact = Contact()
def test_supports(self): def test_default_client_supports(self):
''' Test the Entity Capabilities part of the contact instance ''' '''
Test the caps support method of contacts.
See test_caps for more enhanced tests.
'''
NS_MUC = 'http://jabber.org/protocol/muc' self.assertTrue(self.contact.supports(NS_MUC),
msg="Must not backtrace on simple check for supported feature")
# Test with mocks to get basic set/get property behaviour checked client_caps = NullClientCaps()
all_supported_mock_entity_caps = Mock( self.contact.set_supported_client_caps(client_caps)
returnValues={"contains_feature": True})
nothing_supported_mock_entity_caps = Mock(
returnValues={"contains_feature": False})
contact = Contact() self.assertTrue(self.contact.supports(NS_MUC),
msg="Must not backtrace on simple check for supported feature")
contact.supports = all_supported_mock_entity_caps
self.assertTrue(contact.supports(NS_MUC))
contact.supports = nothing_supported_mock_entity_caps class TestContact(TestCommonContact):
self.assertFalse(contact.supports(NS_MUC))
# Test with EntityCapabilites to detect API changes def setUp(self):
contact.supports = NullClientCaps() TestCommonContact.setUp(self)
self.assertTrue(contact.supports(NS_MUC), self.contact = Contact()
msg="Default behaviour is to support everything on unknown caps")
class TestGC_Contact(TestCommonContact):
def setUp(self):
TestCommonContact.setUp(self)
self.contact = GC_Contact()
if __name__ == "__main__": if __name__ == "__main__":