set_register partial implementation
darcs-hash:20061028120036-86b55-08e755d4ae8f6ed4e2b895a33adfc34d2fd00257.gz
This commit is contained in:
10
run_tests.py
10
run_tests.py
@@ -50,14 +50,14 @@ if __name__ == '__main__':
|
|||||||
feeder_suite = unittest.makeSuite(Feeder_TestCase, "test")
|
feeder_suite = unittest.makeSuite(Feeder_TestCase, "test")
|
||||||
sender_suite = unittest.makeSuite(Sender_TestCase, "test")
|
sender_suite = unittest.makeSuite(Sender_TestCase, "test")
|
||||||
jcl_suite = unittest.TestSuite()
|
jcl_suite = unittest.TestSuite()
|
||||||
# jcl_suite.addTest(FeederComponent_TestCase('test_handle_get_register_exist'))
|
jcl_suite.addTest(FeederComponent_TestCase('test_handle_set_register_new'))
|
||||||
# jcl_suite.addTest(FeederComponent_TestCase('test_handle_presence_available_to_account_live_password'))
|
# jcl_suite.addTest(FeederComponent_TestCase('test_handle_presence_available_to_account_live_password'))
|
||||||
# jcl_suite = unittest.TestSuite((feeder_component_suite))
|
# jcl_suite = unittest.TestSuite((feeder_component_suite))
|
||||||
# jcl_suite = unittest.TestSuite((component_suite))
|
# jcl_suite = unittest.TestSuite((component_suite))
|
||||||
jcl_suite = unittest.TestSuite((component_suite,
|
# jcl_suite = unittest.TestSuite((component_suite,
|
||||||
feeder_component_suite,
|
# feeder_component_suite,
|
||||||
feeder_suite,
|
# feeder_suite,
|
||||||
sender_suite))
|
# sender_suite))
|
||||||
test_support.run_suite(jcl_suite)
|
test_support.run_suite(jcl_suite)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,18 @@
|
|||||||
## Login : David Rousselie <dax@happycoders.org>
|
## Login : David Rousselie <dax@happycoders.org>
|
||||||
## Started on Wed Aug 9 21:04:42 2006 David Rousselie
|
## Started on Wed Aug 9 21:04:42 2006 David Rousselie
|
||||||
## $Id$
|
## $Id$
|
||||||
##
|
##
|
||||||
## Copyright (C) 2006 David Rousselie
|
## Copyright (C) 2006 David Rousselie
|
||||||
## This program is free software; you can redistribute it and/or modify
|
## This program is free software; you can redistribute it and/or modify
|
||||||
## it under the terms of the GNU General Public License as published by
|
## it under the terms of the GNU General Public License as published by
|
||||||
## the Free Software Foundation; either version 2 of the License, or
|
## the Free Software Foundation; either version 2 of the License, or
|
||||||
## (at your option) any later version.
|
## (at your option) any later version.
|
||||||
##
|
##
|
||||||
## This program is distributed in the hope that it will be useful,
|
## This program is distributed in the hope that it will be useful,
|
||||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
## GNU General Public License for more details.
|
## GNU General Public License for more details.
|
||||||
##
|
##
|
||||||
## You should have received a copy of the GNU General Public License
|
## You should have received a copy of the GNU General Public License
|
||||||
## along with this program; if not, write to the Free Software
|
## along with this program; if not, write to the Free Software
|
||||||
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
@@ -56,7 +56,7 @@ VERSION = "0.1"
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
# JCL implementation
|
# JCL implementation
|
||||||
###############################################################################
|
###############################################################################
|
||||||
class JCLComponent(Component):
|
class JCLComponent(Component):
|
||||||
"""Implement default JCL component behavior:
|
"""Implement default JCL component behavior:
|
||||||
- regular interval behavior
|
- regular interval behavior
|
||||||
- Jabber register process (add, delete, update accounts)
|
- Jabber register process (add, delete, update accounts)
|
||||||
@@ -80,7 +80,7 @@ class JCLComponent(Component):
|
|||||||
|
|
||||||
account_class = property(get_account_class, set_account_class)
|
account_class = property(get_account_class, set_account_class)
|
||||||
|
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
jid,
|
jid,
|
||||||
secret,
|
secret,
|
||||||
@@ -106,7 +106,7 @@ class JCLComponent(Component):
|
|||||||
self.accounts = []
|
self.accounts = []
|
||||||
self.time_unit = 60
|
self.time_unit = 60
|
||||||
self.queue = Queue(100)
|
self.queue = Queue(100)
|
||||||
|
|
||||||
self.disco_info.add_feature("jabber:iq:version")
|
self.disco_info.add_feature("jabber:iq:version")
|
||||||
self.disco_info.add_feature("jabber:iq:register")
|
self.disco_info.add_feature("jabber:iq:register")
|
||||||
self.__logger = logging.getLogger("jcl.jabber.JCLComponent")
|
self.__logger = logging.getLogger("jcl.jabber.JCLComponent")
|
||||||
@@ -115,7 +115,7 @@ class JCLComponent(Component):
|
|||||||
|
|
||||||
signal.signal(signal.SIGINT, self.signal_handler)
|
signal.signal(signal.SIGINT, self.signal_handler)
|
||||||
signal.signal(signal.SIGTERM, self.signal_handler)
|
signal.signal(signal.SIGTERM, self.signal_handler)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Main loop
|
"""Main loop
|
||||||
Connect to Jabber server
|
Connect to Jabber server
|
||||||
@@ -323,10 +323,10 @@ class JCLComponent(Component):
|
|||||||
"""Handle user registration response
|
"""Handle user registration response
|
||||||
"""
|
"""
|
||||||
self.__logger.debug("SET_REGISTER")
|
self.__logger.debug("SET_REGISTER")
|
||||||
## lang_class = \
|
lang_class = \
|
||||||
## self.__lang.get_lang_class_from_node(info_query.get_node())
|
self.__lang.get_lang_class_from_node(info_query.get_node())
|
||||||
from_jid = info_query.get_from()
|
from_jid = info_query.get_from()
|
||||||
## base_from_jid = unicode(from_jid.bare())
|
base_from_jid = unicode(from_jid.bare())
|
||||||
remove = info_query.xpath_eval("r:query/r:remove", \
|
remove = info_query.xpath_eval("r:query/r:remove", \
|
||||||
{"r" : "jabber:iq:register"})
|
{"r" : "jabber:iq:register"})
|
||||||
if remove:
|
if remove:
|
||||||
@@ -354,7 +354,55 @@ class JCLComponent(Component):
|
|||||||
x_data = X()
|
x_data = X()
|
||||||
x_data.from_xml(query.children)
|
x_data.from_xml(query.children)
|
||||||
# TODO : get info from Xdata
|
# TODO : get info from Xdata
|
||||||
|
# "name" must not be null
|
||||||
|
name = x_data.get_field_value("name")
|
||||||
|
if name is None:
|
||||||
|
# TODO make error
|
||||||
|
print "ERROR"
|
||||||
|
self.db_connect()
|
||||||
|
accounts = self.account_class.select(\
|
||||||
|
self.account_class.q.user_jid == base_from_jid \
|
||||||
|
and self.account_class.q.name == name)
|
||||||
|
accounts_count = accounts.count()
|
||||||
|
all_accounts = self.account_class.select(\
|
||||||
|
self.account_class.q.user_jid == base_from_jid)
|
||||||
|
all_accounts_count = all_accounts.count()
|
||||||
|
if accounts_count > 1:
|
||||||
|
# TODO make error
|
||||||
|
print "ERROR"
|
||||||
|
if accounts_count == 1:
|
||||||
|
account = list(accounts)[0]
|
||||||
|
else:
|
||||||
|
account = self.account_class(user_jid = base_from_jid, \
|
||||||
|
name = name, \
|
||||||
|
jid = name + u"@" + unicode(self.jid))
|
||||||
|
for (field, field_type, field_post_func, field_default_func) in \
|
||||||
|
self.account_class.get_register_fields():
|
||||||
|
setattr(account, x_data.get_field_value(field, \
|
||||||
|
field_post_func, \
|
||||||
|
field_default_func))
|
||||||
|
info_query = info_query.make_result_response()
|
||||||
|
self.stream.send(info_query)
|
||||||
|
|
||||||
|
if all_accounts_count == 0:
|
||||||
|
self.stream.send(Presence(from_jid = self.jid, to_jid = base_from_jid, \
|
||||||
|
stanza_type = "subscribe"))
|
||||||
|
if accounts_count == 0:
|
||||||
|
self.stream.send(Message(\
|
||||||
|
from_jid = self.jid, to_jid = from_jid, \
|
||||||
|
stanza_type = "normal", \
|
||||||
|
subject = account.get_new_message_subject(lang_class), \
|
||||||
|
body = account.get_new_message_body(lang_class)))
|
||||||
|
self.stream.send(Presence(from_jid = self.get_jid(account), \
|
||||||
|
to_jid = base_from_jid, \
|
||||||
|
stanza_type = "subscribe"))
|
||||||
|
else:
|
||||||
|
self.stream.send(Message(\
|
||||||
|
from_jid = self.jid, to_jid = from_jid, \
|
||||||
|
stanza_type = "normal", \
|
||||||
|
subject = account.get_update_message_subject(lang_class), \
|
||||||
|
body = account.get_update_message_body(lang_class)))
|
||||||
|
self.db_disconnect()
|
||||||
|
|
||||||
def handle_presence_available(self, stanza):
|
def handle_presence_available(self, stanza):
|
||||||
"""Handle presence availability
|
"""Handle presence availability
|
||||||
@@ -518,7 +566,7 @@ class JCLComponent(Component):
|
|||||||
and old_status == account.OFFLINE \
|
and old_status == account.OFFLINE \
|
||||||
and _account.password == None :
|
and _account.password == None :
|
||||||
self._ask_password(_account, lang_class)
|
self._ask_password(_account, lang_class)
|
||||||
|
|
||||||
def _ask_password(self, _account, lang_class):
|
def _ask_password(self, _account, lang_class):
|
||||||
"""Send a Jabber message to ask for account password
|
"""Send a Jabber message to ask for account password
|
||||||
"""
|
"""
|
||||||
@@ -551,6 +599,11 @@ class JCLComponent(Component):
|
|||||||
reg_form.instructions = lang_class.register_instructions
|
reg_form.instructions = lang_class.register_instructions
|
||||||
reg_form.type = "form"
|
reg_form.type = "form"
|
||||||
|
|
||||||
|
# "name" field is mandatory
|
||||||
|
reg_form.add_field(field_type = "text-single", \
|
||||||
|
label = lang_class.account_name, \
|
||||||
|
var = "name")
|
||||||
|
|
||||||
for (field, field_type) in \
|
for (field, field_type) in \
|
||||||
self.account_class.get_register_fields():
|
self.account_class.get_register_fields():
|
||||||
lang_label_attr = self.account_class.__name__.lower() \
|
lang_label_attr = self.account_class.__name__.lower() \
|
||||||
@@ -570,11 +623,13 @@ class JCLComponent(Component):
|
|||||||
"""Return register form for an existing account (update)
|
"""Return register form for an existing account (update)
|
||||||
"""
|
"""
|
||||||
reg_form = self.get_reg_form(lang_class)
|
reg_form = self.get_reg_form(lang_class)
|
||||||
|
reg_form.fields["name"].value = account.name
|
||||||
|
reg_form.fields["name"].type = "hidden"
|
||||||
for (field_name, field) in reg_form.fields.items():
|
for (field_name, field) in reg_form.fields.items():
|
||||||
if hasattr(self.account_class, field_name):
|
if hasattr(self.account_class, field_name):
|
||||||
field.value = getattr(account, field_name)
|
field.value = getattr(account, field_name)
|
||||||
return reg_form
|
return reg_form
|
||||||
|
|
||||||
###########################################################################
|
###########################################################################
|
||||||
# Virtual methods
|
# Virtual methods
|
||||||
###########################################################################
|
###########################################################################
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class Field(object):
|
|||||||
"""Jabber Xdata form Field
|
"""Jabber Xdata form Field
|
||||||
"""
|
"""
|
||||||
def __init__(self, field_type, label, var, value):
|
def __init__(self, field_type, label, var, value):
|
||||||
self.__type = field_type
|
self.type = field_type
|
||||||
self.__label = label
|
self.__label = label
|
||||||
self.__var = var
|
self.__var = var
|
||||||
self.value = value
|
self.value = value
|
||||||
@@ -71,7 +71,7 @@ class Field(object):
|
|||||||
raise Exception, "parent field should not be None"
|
raise Exception, "parent field should not be None"
|
||||||
else:
|
else:
|
||||||
field = parent.newChild(None, "field", None)
|
field = parent.newChild(None, "field", None)
|
||||||
field.setProp("type", self.__type)
|
field.setProp("type", self.type)
|
||||||
if not self.__label is None:
|
if not self.__label is None:
|
||||||
field.setProp("label", self.__label)
|
field.setProp("label", self.__label)
|
||||||
if not self.__var is None:
|
if not self.__var is None:
|
||||||
@@ -106,6 +106,15 @@ class X(object):
|
|||||||
self.fields_tab.append(field)
|
self.fields_tab.append(field)
|
||||||
return field
|
return field
|
||||||
|
|
||||||
|
def get_field_value(self, field_name, \
|
||||||
|
post_func = (lambda value: value), \
|
||||||
|
defaut_func = (lambda: None)):
|
||||||
|
"""Return field value processed by post_func
|
||||||
|
or return default func processing if field does not exist"""
|
||||||
|
if self.fields.has_key(field_name):
|
||||||
|
return post_func(self.fields[field_name].value)
|
||||||
|
return default_func()
|
||||||
|
|
||||||
def attach_xml(self, info_query):
|
def attach_xml(self, info_query):
|
||||||
"""Attach this Xdata form to iq node
|
"""Attach this Xdata form to iq node
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -88,12 +88,10 @@ class Lang:
|
|||||||
update_title = u"Jabber mail connection update"
|
update_title = u"Jabber mail connection update"
|
||||||
update_instructions = u"Modifying connection '%s'"
|
update_instructions = u"Modifying connection '%s'"
|
||||||
connection_label = u"%s connection '%s'"
|
connection_label = u"%s connection '%s'"
|
||||||
update_account_message_subject = u"Updated %s connection '%s'"
|
update_account_message_subject = u"Updated account '%s'"
|
||||||
update_account_message_body = u"Registered with username '%s' and " \
|
update_account_message_body = u"Updated account"
|
||||||
u"password '%s' on '%s'"
|
new_account_message_subject = u"New account '%s' created"
|
||||||
new_account_message_subject = u"New %s connection '%s' created"
|
new_account_message_body = u"New account created"
|
||||||
new_account_message_body = u"Registered with " \
|
|
||||||
"username '%s' and password '%s' on '%s'"
|
|
||||||
ask_password_subject = u"Password request"
|
ask_password_subject = u"Password request"
|
||||||
ask_password_body = u"Reply to this message with the password " \
|
ask_password_body = u"Reply to this message with the password " \
|
||||||
"for the following account: \n" \
|
"for the following account: \n" \
|
||||||
@@ -135,13 +133,11 @@ class Lang:
|
|||||||
update_title = u"Mise à jour du compte JMC"
|
update_title = u"Mise à jour du compte JMC"
|
||||||
update_instructions = u"Modification de la connexion '%s'"
|
update_instructions = u"Modification de la connexion '%s'"
|
||||||
connection_label = u"Connexion %s '%s'"
|
connection_label = u"Connexion %s '%s'"
|
||||||
update_account_message_subject = u"La connexion %s '%s' a été mise " \
|
update_account_message_subject = u"Le compte '%s' a été mis " \
|
||||||
u"à jour"
|
u"à jour"
|
||||||
update_account_message_body = u"Nom d'utilisateur : '%s'\nMot de " \
|
update_account_message_body = u"Compte mis à jour"
|
||||||
u"passe : '%s'\nsur : '%s'"
|
new_account_message_subject = u"Le compte '%s' a été créé"
|
||||||
new_account_message_subject = u"La connexion %s '%s' a été créée"
|
new_account_message_body = u"Compte créé"
|
||||||
new_account_message_body = u"Nom d'utilisateur : '%s'\nMot de passe " \
|
|
||||||
u": '%s'\nsur : '%s'"
|
|
||||||
ask_password_subject = u"Demande de mot de passe"
|
ask_password_subject = u"Demande de mot de passe"
|
||||||
ask_password_body = u"Répondre à ce message avec le mot de passe " \
|
ask_password_body = u"Répondre à ce message avec le mot de passe " \
|
||||||
u"du compte suivant : \n" \
|
u"du compte suivant : \n" \
|
||||||
|
|||||||
@@ -91,6 +91,32 @@ class Account(SQLObject):
|
|||||||
|
|
||||||
|
|
||||||
def get_register_fields(cls):
|
def get_register_fields(cls):
|
||||||
return [('name', "text-single")]
|
"""Return a list of tuples for X Data Form composition
|
||||||
|
A tuple is composed of:
|
||||||
|
- field_name: might be the name of one of the class attribut
|
||||||
|
- field_type: 'text-single', 'hidden', 'text-private', 'boolean',
|
||||||
|
'list-single', ...
|
||||||
|
- field_post_func: function called to process received field
|
||||||
|
- field_default_func: function to return default value (or error if
|
||||||
|
field is mandatory)
|
||||||
|
"""
|
||||||
|
return [] # "name" field is mandatory
|
||||||
|
|
||||||
get_register_fields = classmethod(get_register_fields)
|
get_register_fields = classmethod(get_register_fields)
|
||||||
|
|
||||||
|
def get_new_message_subject(self, lang_class):
|
||||||
|
"""Get localized message subject for new account"""
|
||||||
|
return lang_class.new_account_message_subject % (self.name)
|
||||||
|
|
||||||
|
def get_new_message_body(self, lang_class):
|
||||||
|
"""Return localized message body for new account"""
|
||||||
|
return lang_class.new_account_message_body
|
||||||
|
|
||||||
|
def get_update_message_subject(self, lang_class):
|
||||||
|
"""Return localized message subject for existing account"""
|
||||||
|
return lang_class.new_account_message_subject % (self.name)
|
||||||
|
|
||||||
|
def get_update_message_body(self, lang_class):
|
||||||
|
"""Return localized message body for existing account"""
|
||||||
|
return lang_class.new_account_message_body
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ from jcl.jabber.component import JCLComponent
|
|||||||
from jcl.model import account
|
from jcl.model import account
|
||||||
from jcl.model.account import Account
|
from jcl.model.account import Account
|
||||||
from jcl.lang import Lang
|
from jcl.lang import Lang
|
||||||
|
from jcl.jabber.x import X
|
||||||
|
|
||||||
DB_PATH = "/tmp/test.db"
|
DB_PATH = "/tmp/test.db"
|
||||||
DB_URL = DB_PATH# + "?debug=1&debugThreading=1"
|
DB_URL = DB_PATH# + "?debug=1&debugThreading=1"
|
||||||
@@ -362,7 +363,7 @@ class JCLComponent_TestCase(unittest.TestCase):
|
|||||||
self.assertEquals(len(fields), 1)
|
self.assertEquals(len(fields), 1)
|
||||||
self.assertEquals(len([field
|
self.assertEquals(len([field
|
||||||
for field in fields \
|
for field in fields \
|
||||||
if field.prop("type") == "text-single" \
|
if field.prop("type") == "hidden" \
|
||||||
and field.prop("var") == "name" \
|
and field.prop("var") == "name" \
|
||||||
and field.prop("label") == \
|
and field.prop("label") == \
|
||||||
Lang.en.account_name]), \
|
Lang.en.account_name]), \
|
||||||
@@ -373,9 +374,72 @@ class JCLComponent_TestCase(unittest.TestCase):
|
|||||||
self.assertEquals(len(value), 1)
|
self.assertEquals(len(value), 1)
|
||||||
self.assertEquals(value[0].content, "account1")
|
self.assertEquals(value[0].content, "account1")
|
||||||
|
|
||||||
def test_handle_set_register(self):
|
def test_handle_set_register_new(self):
|
||||||
|
self.comp.stream = MockStream()
|
||||||
|
self.comp.stream_class = MockStream
|
||||||
|
x_data = X()
|
||||||
|
x_data.xmlns = "jabber:x:data"
|
||||||
|
x_data.type = "submit"
|
||||||
|
x_data.add_field(field_type = "text-single", \
|
||||||
|
var = "name", \
|
||||||
|
value = "account1")
|
||||||
|
iq_set = Iq(stanza_type = "set", \
|
||||||
|
from_jid = "user1@test.com", \
|
||||||
|
to_jid = "jcl.test.com")
|
||||||
|
query = iq_set.new_query("jabber:iq:register")
|
||||||
|
x_data.attach_xml(query)
|
||||||
|
self.comp.handle_set_register(iq_set)
|
||||||
|
|
||||||
|
account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL)
|
||||||
|
accounts = self.comp.account_class.select(\
|
||||||
|
self.comp.account_class.q.user_jid == "user1@test.com" \
|
||||||
|
and self.comp.account_class.q.name == "account1")
|
||||||
|
self.assertEquals(accounts.count(), 1)
|
||||||
|
_account = accounts[0]
|
||||||
|
self.assertEquals(_account.user_jid, "user1@test.com")
|
||||||
|
self.assertEquals(_account.name, "account1")
|
||||||
|
self.assertEquals(_account.jid, "account1@jcl.test.com")
|
||||||
|
del account.hub.threadConnection
|
||||||
|
|
||||||
|
stanza_sent = self.comp.stream.sent
|
||||||
|
print str([str(stanza.get_node()) for stanza in stanza_sent])
|
||||||
|
self.assertEquals(len(stanza_sent), 4)
|
||||||
|
iq_result = stanza_sent[0]
|
||||||
|
self.assertTrue(isinstance(iq_result, Iq))
|
||||||
|
self.assertEquals(iq_result.get_node().prop("type"), "result")
|
||||||
|
self.assertEquals(iq_result.get_from(), "jcl.test.com")
|
||||||
|
self.assertEquals(iq_result.get_to(), "user1@test.com")
|
||||||
|
|
||||||
|
presence_component = stanza_sent[1]
|
||||||
|
self.assertTrue(isinstance(presence_component, Presence))
|
||||||
|
self.assertEquals(presence_component.get_from(), "jcl.test.com")
|
||||||
|
self.assertEquals(presence_component.get_to(), "user1@test.com")
|
||||||
|
self.assertEquals(presence_component.get_node().prop("type"), \
|
||||||
|
"subscribe")
|
||||||
|
|
||||||
|
message = stanza_sent[2]
|
||||||
|
self.assertTrue(isinstance(message, Message))
|
||||||
|
self.assertEquals(message.get_from(), "jcl.test.com")
|
||||||
|
self.assertEquals(message.get_to(), "user1@test.com")
|
||||||
|
self.assertEquals(message.get_subject(), \
|
||||||
|
_account.get_new_message_subject(Lang.en))
|
||||||
|
self.assertEquals(message.get_body(), \
|
||||||
|
_account.get_new_message_body(Lang.en))
|
||||||
|
|
||||||
|
presence_account = stanza_sent[3]
|
||||||
|
self.assertTrue(isinstance(presence_account, Presence))
|
||||||
|
self.assertEquals(presence_account.get_from(), "account1@jcl.test.com")
|
||||||
|
self.assertEquals(presence_account.get_to(), "user1@test.com")
|
||||||
|
self.assertEquals(presence_account.get_node().prop("type"), \
|
||||||
|
"subscribe")
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_set_register_update(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_handle_set_register_remove(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def test_handle_presence_available_to_component(self):
|
def test_handle_presence_available_to_component(self):
|
||||||
self.comp.stream = MockStream()
|
self.comp.stream = MockStream()
|
||||||
self.comp.stream_class = MockStream
|
self.comp.stream_class = MockStream
|
||||||
|
|||||||
Reference in New Issue
Block a user