Error: {{ error }}{% endif %} +
+{% endblock %} diff --git a/mjacob2/pypsyc/util.py b/mjacob2/pypsyc/util.py new file mode 100644 index 0000000..8d01e9b --- /dev/null +++ b/mjacob2/pypsyc/util.py @@ -0,0 +1,157 @@ +""" + pypsyc.util + ~~~~~~~~~~~ + + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +import logging + +from greenlet import greenlet, getcurrent +from twisted.internet import reactor, error +from twisted.internet.defer import inlineCallbacks, returnValue +from twisted.internet.tcp import Connector + + +scheduler = getcurrent() + +def schedule(f, *args, **kwds): + g = greenlet(f, scheduler) + if getcurrent() is scheduler: + g.switch(*args, **kwds) + else: + reactor.callLater(0, g.switch, *args, **kwds) + + +class Waiter(object): + def __init__(self): + self.greenlet = None + self.value = None + self.exception = None + + def callback(self, *args, **kwds): + if self.greenlet is None: + self.value, = args + else: + assert getcurrent() is not self.greenlet + self.greenlet.switch(*args, **kwds) + + def errback(self, exception): + if self.greenlet is None: + self.exception = exception + else: + self.greenlet.throw(exception) + + def get(self): + if self.value is not None: + return self.value + if self.exception is not None: + raise self.exception + self.greenlet = getcurrent() + return self.greenlet.parent.switch() + + +class DNSError(Exception): + pass + +@inlineCallbacks +def _resolve_hostname(host): + try: + name = '_psyc._tcp.%s.' % host + answers, auth, add = yield lookupService(name) + + srv_rr = answers[0] + assert srv_rr.name.name == name + host = srv_rr.payload.target.name + port = srv_rr.payload.port + + a_rr = [rr for rr in add if rr.name.name == host][0] + ip = a_rr.payload.dottedQuad() + + except (DNSNameError, IndexError): + try: + ip = yield getHostByName(host) + except DNSNameError: + raise DNSError("Unknown host %s." % host) + port = 4404 + + returnValue((ip, port)) + +try: + from twisted.names.client import lookupService, getHostByName + from twisted.names.error import DNSNameError +except ImportError: + log = logging.getLogger(__name__) + log.warn("twisted names isn't installed -- DNS SRV is disabled") + def resolve_hostname(host): + return host, 4404 +else: + def resolve_hostname(host): + waiter = Waiter() + _resolve_hostname(host).addCallbacks(waiter.callback, + lambda f: waiter.errback(f.value)) + return waiter.get() + + +class _PSYCConnector(Connector): + def __init__(self, *args): + Connector.__init__(self, *args, reactor=reactor) + self.waiter = Waiter() + + def connect(self): + assert self.state == 'disconnected', "can't connect in this state" + self.state = 'connecting' + + self.transport = transport = self._makeTransport() + self.timeoutID = self.reactor.callLater(self.timeout, + transport.failIfNotConnected, + error.TimeoutError()) + self.waiter.get() + return self.circuit + + def buildProtocol(self, addr): + self.circuit = Connector.buildProtocol(self, addr) + self.circuit.inited = self.waiter.callback + return self.circuit + + def connectionFailed(self, reason): + self.cancelTimeout() + self.transport = None + self.state = 'disconnected' + self.waiter.errback(reason.value) + + def connectionLost(self, reason): + self.state = 'disconnected' + self.factory.connection_lost(self.circuit, reason.value) + +def connect(host, port, factory, timeout=30, bindAddress=None): + return _PSYCConnector(host, port, factory, timeout, bindAddress).connect() + + +class Event(object): + def __init__(self): + self.observers = [] + + def add_observer(self, observer, *args, **kwds): + self.observers.append((observer, args, kwds)) + + def __iadd__(self, observer): + self.observers.append((observer, (), {})) + return self + + def __isub__(self, observer): + observers = [i for i in self.observers if i[0] == observer] + assert len(observers) == 1, observers + self.observers.remove(observers[0]) + return self + + def __call__(self, *args, **kwds): + for observer, args2, kwds2 in self.observers: + kwds.update(kwds2) + observer(*(args + args2), **kwds) + + +def key_intersection(a, b): + for k in b: + if k in a: + yield k diff --git a/mjacob2/setup.py b/mjacob2/setup.py new file mode 100644 index 0000000..30fc64b --- /dev/null +++ b/mjacob2/setup.py @@ -0,0 +1,21 @@ +from setuptools import setup, find_packages + +import pypsyc + +setup( + name = "pypsyc", + version = pypsyc.__version__, + packages = find_packages(exclude='tests'), + package_data = { + 'pypsyc.client': ['psyc.ico'], + 'pypsyc.server.webif': ['templates/*'] + }, + zip_safe = False, + scripts = ['bin/pypsyc', 'bin/pypsycd'], + entry_points = ''' + [pypsyc.server.packages] + root = pypsyc.server.root:Root + person = pypsyc.server.person:Person + place = pypsyc.server.place:Place + ''' +) diff --git a/mjacob2/tests/__init__.py b/mjacob2/tests/__init__.py new file mode 100644 index 0000000..8733ea0 --- /dev/null +++ b/mjacob2/tests/__init__.py @@ -0,0 +1,4 @@ +import tests.helpers +from twisted.python.log import defaultObserver, PythonLoggingObserver +defaultObserver.stop() +PythonLoggingObserver().start() diff --git a/mjacob2/tests/constants.py b/mjacob2/tests/constants.py new file mode 100644 index 0000000..40091b2 --- /dev/null +++ b/mjacob2/tests/constants.py @@ -0,0 +1,41 @@ +from pypsyc.client.model import Presence +from pypsyc.core.mmp import Uni + + +SERVER1 = 'server1' +SERVER1_UNI = Uni('psyc://%s/' % SERVER1) +USER1 = '~user1' +USER2 = '~user2' +USER1_UNI = SERVER1_UNI.chain(USER1) +USER2_UNI = SERVER1_UNI.chain(USER2) +USER1_NICK = 'user1' +USER2_NICK = 'user2' +RESOURCE = '*Resource' +RESOURCE1_UNI = USER1_UNI.chain(RESOURCE) +RESOURCE2_UNI = USER2_UNI.chain(RESOURCE) +PLACE = '@place' +PLACE_UNI = SERVER1_UNI.chain(PLACE) +SERVER2 = 'server2' +SERVER2_UNI = Uni('psyc://%s/' % SERVER2) +SERVER3 = 'server3' +SERVER3_UNI = Uni('psyc://%s/' % SERVER3) + +INTERFACE = 'interface' +IP = '10.0.0.1' +PORT = 4404 +VARIABLES = {'_key': 'value'} +CONTENT = [''] + +PASSWORD = 'password' +MESSAGE = 'message' +UNKNOWN = 0 +OFFLINE = 1 +ONLINE = 7 +PRESENCE_UNKNOWN = Presence(UNKNOWN) +PRESENCE_OFFLINE = Presence(OFFLINE) +PRESENCE_ONLINE = Presence(ONLINE) +PENDING = 'pending' +OFFERED = 'offered' +ESTABLISHED = 'established' +ERROR = 'error' +EXCEPTION = Exception(ERROR) diff --git a/mjacob2/tests/helpers.py b/mjacob2/tests/helpers.py new file mode 100644 index 0000000..874b74e --- /dev/null +++ b/mjacob2/tests/helpers.py @@ -0,0 +1,133 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +from functools import wraps +from traceback import format_exc +import sys + +from greenlet import getcurrent +from mock import Mock +import twisted.internet + +from pypsyc.core.mmp import Header + + +# keep reactor from being installed + +assert not 'twisted.internet.reactor' in sys.modules +twisted.internet.reactor = sys.modules['twisted.internet.reactor'] = None + + +# random helpers + +def inited_header(*args, **kwds): + header = Header(*args, **kwds) + header._init() + return header + +def _make_send_function(other_circuit): + def f(header, content): + try: + other_circuit.packet_received(inited_header(header), content) + except: + raise Exception("%s.packet_received raised exception:\n%s" % + (other_circuit, format_exc())) + return f + +def connect_circuits(c1, c2): + """Make a virtual connection between two curcuits.""" + c1.send = c1.psyc._send = _make_send_function(c2) + c2.send = c2.psyc._send = _make_send_function(c1) + return c1, c2 + + +class iter_(object): + def __init__(self, *iter_): + self.iter_ = iter_ + + def __eq__(self, iter_): + return tuple(iter_) == self.iter_ + +def rendered(packet): + return iter_(*packet.render()) + + +def check_success(f): + """ + Wrap `f` so that it checks if :attr:`TestCase.success` is ``True``. This is + useful to check if an asynchronous test has passed completely. + """ + @wraps(f) + def _wrapper(self): + self.success = False + f(self) + assert self.success + del self.success + return _wrapper + + +class AsyncMethod(object): + def __call__(self, *args, **kwds): + self.child = getcurrent() + return self.child.parent.switch() + + def callback(self, *args, **kwds): + self.child.switch(*args, **kwds) + + def errback(self, *args, **kwds): + self.child.throw(*args, **kwds) + + +def mockify(obj, attrs): + """Replace attrs of obj with mocks. Return the original attributes.""" + originals = {} + for attr in attrs: + orig = originals[attr] = getattr(obj, attr) + mock = Mock(spec=orig) + setattr(obj, attr, mock) + return originals + +class mockified(object): + """ + Wrap a function so that it 'mockifies' and resets `obj`. If `obj` is a + string, mockify the module named like it. + Can also be used as a context manager. + """ + def __init__(self, obj, attrs): + if isinstance(obj, basestring): + self.obj = __import__(obj, globals(), locals(), attrs) + else: + self.obj = obj + self.attrs = attrs + + def __enter__(self): + self.originals = mockify(self.obj, self.attrs) + return (getattr(self.obj, attr) for attr in self.attrs) + + def __exit__(self, type, value, traceback): + for attr, original in self.originals.iteritems(): + setattr(self.obj, attr, original) + + def __call__(self, f): + @wraps(f) + def _wrapper(*args, **kwds): + with self: + kwds.update((attr, getattr(self.obj, attr)) for attr in self.attrs) + return f(*args, **kwds) + return _wrapper + + +class PlaceHolder(object): + """ + An object that be used as a placeholder for comparisons. The object on the + other side of the comparison is saved as attribute `obj`. + """ + + def __eq__(self, other): + self.obj = other + return True + + +class StubException(Exception): + pass diff --git a/mjacob2/tests/test_client/test_controller.py b/mjacob2/tests/test_client/test_controller.py new file mode 100644 index 0000000..fdf1f8e --- /dev/null +++ b/mjacob2/tests/test_client/test_controller.py @@ -0,0 +1,680 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +from mock import Mock, MagicMock +from nose.tools import assert_raises +from tests.constants import (SERVER1, USER1_UNI, USER2_UNI, USER1_NICK, + USER2_NICK, PLACE_UNI, SERVER2_UNI, MESSAGE, PRESENCE_OFFLINE, + PRESENCE_ONLINE, ERROR, EXCEPTION) +from tests.helpers import mockified, AsyncMethod, PlaceHolder, iter_ + +from pypsyc.client.controller import (ListBinding, AccountsController, + ConversationController, ConferenceController, TabsController, + FriendListController, DumpController, MainController) +from pypsyc.client.model import Message +from pypsyc.client.observable import ObsList, ObsDict, ObsObj, ObsAttr +from pypsyc.util import Event + + +def _check_obs_list(obs_list): + assert obs_list.setitem_evt.observers == [] + assert obs_list.delitem_evt.observers == [] + assert obs_list.insert_evt.observers == [] + assert obs_list.update_evt.observers == [] + return True + + +def _check_obs_dict(obs_dict): + assert obs_dict.setitem_evt.observers == [] + assert obs_dict.delitem_evt.observers == [] + assert obs_dict.update_evt.observers == [] + return True + + +def _check_obs_obj(obs_obj): + assert obs_obj.update_evt != {} + for evt in obs_obj.update_evt.itervalues(): + assert evt.observers == [] + return True + + +class TestListBinding(object): + def setup(self): + self.model = Mock() + self.model_list = ObsList([self.model]) + self.view_list = [] + self.make_row = Mock() + self.binding = ListBinding(self.model_list, self.view_list, + self.make_row) + + def test_bind(self): + assert self.make_row.call_args_list == [((self.model,),)] + assert self.view_list == [self.make_row.return_value] + + def test_add(self): + assert self.make_row.call_args_list == [((self.model,),)] + self.make_row.reset_mock() + + model2 = Mock() + self.model_list.append(model2) + assert self.make_row.call_args_list == [((model2,),)] + assert self.view_list == [self.make_row.return_value] * 2 + + def test_update(self): + assert self.make_row.call_args_list == [((self.model,),)] + self.make_row.reset_mock() + + self.model_list.updated_item(self.model) + assert self.make_row.call_args_list == [((self.model,),)] + assert self.view_list == [self.make_row.return_value] + + def test_delete(self): + del self.model_list[0] + assert self.view_list == [] + + def test_unbind(self): + self.binding.unbind() + assert _check_obs_list(self.model_list) + assert self.view_list == [] + + +class _List(list): + def __init__(self, *args, **kwds): + list.__init__(self, *args, **kwds) + self.updated_item = Mock() + +class TestAccountsController(object): + @mockified('pypsyc.client.controller', ['ListBinding']) + def setup(self, ListBinding): + self.ListBinding = ListBinding + self.account = Mock() + self.client = Mock() + self.client.accounts = _List([self.account]) + self.view = Mock() + self.controller = AccountsController(self.client, self.view) + + def test_binding(self): + make_row_ph = PlaceHolder() + assert self.ListBinding.call_args_list == [ + ((self.client.accounts, self.view.accounts, make_row_ph),)] + + account = Mock() + row = make_row_ph.obj(account) + assert row == (account.uni, account.active) + + @mockified('pypsyc.client.controller', ['Account']) + def test_add_account(self, Account): + account = Account.return_value + callback_ph = PlaceHolder() + + self.controller.add_account() + assert Account.call_args_list == [ + ((self.client, '', '', '', False, False),)] + assert self.view.method_calls == [ + ('show_addedit_dialog', (account.__dict__, True, callback_ph))] + + callback_ph.obj() + assert self.client.accounts == [self.account, account] + assert self.client.method_calls == [('save_accounts',)] + + def test_edit_account(self): + callback_ph = PlaceHolder() + + self.controller.edit_account(0) + assert self.view.method_calls == [ + ('show_addedit_dialog', (self.account.__dict__, False, + callback_ph))] + + callback_ph.obj() + assert self.client.accounts.updated_item.call_args_list == [ + ((self.account,),)] + assert self.client.method_calls == [('save_accounts',)] + + def test_remove_account(self): + assert_raises(IndexError, self.controller.remove_account, 1) + self.controller.remove_account(0) + assert self.client.accounts == [] + assert self.client.method_calls == [('save_accounts',)] + + def test_set_active(self): + self.controller.set_active(0, True) + assert self.account.active == True + assert self.client.accounts.updated_item.call_args_list == [ + ((self.account,),)] + assert self.client.method_calls == [('save_accounts',)] + + def test_closed(self): + self.controller.closed() + assert self.ListBinding.return_value.method_calls == [('unbind',)] + + +IN = 'i' +OUT = 'o' +LINE = 'line' + +class _StubAccount(ObsObj): + circuit = ObsAttr('circuit') + +class TestDumpController: + def setup(self): + self.circuit = Mock() + self.circuit.dump_evt = Event() + self.account = _StubAccount() + self.account.uni = USER1_UNI + self.client = Mock() + self.client.accounts = ObsList() + self.view = Mock() + + def test_connected(self): + self.account.circuit = self.circuit + self.client.accounts.append(self.account) + DumpController(self.client, self.view) + + self.circuit.dump_evt(IN, LINE) + assert self.view.method_calls == [('show_line', (IN, LINE, USER1_UNI))] + + def test_connect(self): + DumpController(self.client, self.view) + self.client.accounts.append(self.account) + self.account.circuit = self.circuit + + self.circuit.dump_evt(IN, LINE) + assert self.view.method_calls == [('show_line', (IN, LINE, USER1_UNI))] + + def test_disconnect(self): + self.account.circuit = self.circuit + self.client.accounts.append(self.account) + DumpController(self.client, self.view) + + del self.account.circuit + assert self.circuit.dump_evt.observers == [] + + def test_reconnect(self): + DumpController(self.client, self.view) + self.account.circuit = self.circuit + self.client.accounts.append(self.account) + + circuit2 = Mock() + circuit2.dump_evt = Event() + self.account.circuit = circuit2 + assert self.circuit.dump_evt.observers == [] + + circuit2.dump_evt(OUT, LINE) + assert self.view.method_calls == [ + ('show_line', (OUT, LINE, USER1_UNI))] + + def test_remove(self): + self.client.accounts.append(self.account) + self.account.circuit = self.circuit + DumpController(self.client, self.view) + + self.client.accounts.remove(self.account) + assert _check_obs_obj(self.account) + assert self.circuit.dump_evt.observers == [] + + def test_remove_nocircuit(self): + self.client.accounts.append(self.account) + DumpController(self.client, self.view) + + self.client.accounts.remove(self.account) + assert _check_obs_obj(self.account) + + def test_closed(self): + self.client.accounts.append(self.account) + self.account.circuit = self.circuit + dump_controller = DumpController(self.client, self.view) + + dump_controller.closed() + assert _check_obs_list(self.client.accounts) + assert _check_obs_obj(self.account) + assert self.circuit.dump_evt.observers == [] + + +class TestConversationController(object): + def setup(self): + self.tabs_controller = Mock() + self.conversation = Mock() + self.conversation.messages = ObsList() + self.conversation.unknown_target_evt = Event() + self.conversation.delivery_failed_evt = Event() + self.view = Mock() + self.controller = ConversationController(self.tabs_controller, + self.conversation, self.view) + + def test_message(self): + MESSAGE_OBJ = Message(USER2_UNI, MESSAGE) + MESSAGE_LINE = "(%s) <%s> %s" % ( + MESSAGE_OBJ.time.strftime("%H:%M:%S"), USER2_UNI, MESSAGE) + + self.conversation.messages.append(MESSAGE_OBJ) + assert self.view.method_calls == [('show_message', (MESSAGE_LINE,))] + + def test_unknown_target(self): + self.conversation.unknown_target_evt() + assert self.view.method_calls == [('show_unknown_target',)] + + def test_delivery_failed(self): + self.conversation.delivery_failed_evt(ERROR) + assert self.view.method_calls == [('show_delivery_failed', (ERROR,))] + + def test_enter_message(self): + self.controller.enter(MESSAGE) + assert self.conversation.method_calls == [('send_message', (MESSAGE,))] + + def test_enter_command(self): + self.controller.enter("/command") + assert self.view.method_calls == [('show_unknown_command',)] + assert self.conversation.method_calls == [] + + def test_closed(self): + self.conversation.messages.append(Message(None, None)) + self.controller.closed() + assert _check_obs_list(self.conversation.messages) + assert self.conversation.unknown_target_evt.observers == [] + assert self.conversation.delivery_failed_evt.observers == [] + + +class TestConferenceController(object): + @mockified('pypsyc.client.controller', ['ListBinding']) + def setup(self, ListBinding): + self.ListBinding = ListBinding + self.tabs_controller = Mock() + self.conference = Mock() + self.conference.members = [] + self.conference.messages = ObsList() + self.conference.unknown_target_evt = Event() + self.conference.delivery_failed_evt = Event() + self.view = Mock() + self.controller = ConferenceController(self.tabs_controller, + self.conference, self.view) + + def test_binding(self): + makerow_ph = PlaceHolder() + assert self.ListBinding.call_args_list == [ + ((self.conference.members, self.view.members, makerow_ph),)] + + member = Mock() + row = makerow_ph.obj(member) + assert row == (member.uni, member.nick) + + def test_open_conversation(self): + member = Mock() + self.conference.members.append(member) + account = self.conference.conferencing.account + conversation = account.get_conversation.return_value + + self.controller.open_conversation(0) + assert self.conference.conferencing.account.method_calls == [ + ('get_conversation', (member.uni,))] + assert self.tabs_controller.method_calls == [ + ('focus_conversation', (conversation,))] + + def test_closed(self): + self.controller.closed() + assert self.ListBinding.return_value.method_calls == [('unbind',)] + assert _check_obs_list(self.conference.messages) + assert self.conference.unknown_target_evt.observers == [] + assert self.conference.delivery_failed_evt.observers == [] + + +class TestTabsController(object): + def setup(self): + self.client = Mock() + self.client.accounts = ObsList() + self.view = Mock() + + self.account = Mock() + self.account.conversations = ObsDict() + self.account.conferences = ObsDict() + self.client.accounts.append(self.account) + + @mockified('pypsyc.client.controller', ['ConversationController', + 'ConferenceController']) + def test_deleted_account(self, ConversationController, + ConferenceController): + self.account.conversations[USER2_UNI] = Mock() + self.account.conferences[PLACE_UNI] = Mock() + conv_view = self.view.show_conversation.return_value + conv_controller = ConversationController.return_value + conf_view = self.view.show_conference.return_value + conf_controller = ConferenceController.return_value + TabsController(self.client, self.view) + self.view.reset_mock() + + del self.client.accounts[0] + assert _check_obs_dict(self.account.conversations) + assert _check_obs_dict(self.account.conferences) + assert self.view.method_calls == [('remove_tab', (conv_view,)), + ('remove_tab', (conf_view,))] + assert conv_controller.method_calls == [('closed',)] + assert conf_controller.method_calls == [('closed',)] + + @mockified('pypsyc.client.controller', ['ConversationController']) + def test_conversation(self, ConversationController): + conversation = Mock() + conversation.uni = USER2_UNI + conv_view = self.view.show_conversation.return_value + conv_controller = ConversationController.return_value + controller = TabsController(self.client, self.view) + + self.account.conversations[USER2_UNI] = conversation + assert self.view.method_calls == [('show_conversation', (USER2_UNI,))] + assert ConversationController.call_args_list == [ + ((controller, conversation, conv_view),)] + self.view.reset_mock() + + controller.focus_conversation(conversation) + assert self.view.method_calls == [('focus_tab', (conv_view,))] + self.view.reset_mock() + + controller.close_tab(conv_view) + assert self.account.conversations == {} + assert self.view.method_calls == [('remove_tab', (conv_view,))] + assert conv_controller.method_calls == [('closed',)] + + @mockified('pypsyc.client.controller', ['ConferenceController']) + def test_conference(self, ConferenceController): + conference = Mock() + conference.uni = PLACE_UNI + conf_view = self.view.show_conference.return_value + conf_controller = ConferenceController.return_value + self.account.conferences[PLACE_UNI] = conference + + controller = TabsController(self.client, self.view) + assert self.view.method_calls == [ + ('show_conference', (PLACE_UNI,),)] + assert ConferenceController.call_args_list == [ + ((controller, conference, conf_view),)] + self.view.reset_mock() + + controller.focus_conversation(conference) + assert self.view.method_calls == [('focus_tab', (conf_view,))] + self.view.reset_mock() + + controller.close_tab(conf_view) + assert self.account.conferences == {} + assert self.view.method_calls == [('remove_tab', (conf_view,))] + assert conf_controller.method_calls == [('closed',)] + + + +class TestFriendListController(object): + @mockified('pypsyc.client.controller', ['ListBinding']) + def setup(self, ListBinding): + self.ListBinding = ListBinding + model_list_ph = PlaceHolder() + make_row_ph = PlaceHolder() + self.client = Mock() + self.client.accounts = ObsList() + self.view = Mock() + self.tabs_controller = Mock() + + self.controller = FriendListController(self.client, self.view, + self.tabs_controller) + assert self.ListBinding.call_args_list == [ + ((model_list_ph, self.view.friends, make_row_ph),)] + self.model_list = model_list_ph.obj + self.make_row = make_row_ph.obj + + def test_binding(self): + EXTERN_FRIEND_UNI = SERVER2_UNI.chain('~friend') + + friend = Mock() + friend.account.server = SERVER1 + friend.uni = USER1_UNI + friend.presence = PRESENCE_OFFLINE + row = self.make_row(friend) + assert row == (USER1_NICK, False, friend.state) + + friend.uni = EXTERN_FRIEND_UNI + friend.presence = PRESENCE_ONLINE + row = self.make_row(friend) + assert row == (EXTERN_FRIEND_UNI, True, friend.state) + + def test_account(self): + update_evt = Mock() + friend1 = Mock() + friend2 = Mock() + account = Mock() + account.friends = ObsDict({USER1_UNI: friend1}) + + self.model_list.update_evt += update_evt + assert self.model_list == [] + + self.client.accounts.append(account) + assert self.model_list == [friend1] + + account.friends[USER2_UNI] = friend2 + assert self.model_list == [friend1, friend2] + + account.friends.updated_item(USER2_UNI) + assert update_evt.call_args_list == [((1, friend2),)] + + del account.friends[USER1_UNI] + assert self.model_list == [friend2] + + del self.client.accounts[0] + assert _check_obs_dict(account.friends) + assert self.model_list == [] + + def test_open_conversation(self): + friend = Mock() + friend.uni = USER2_UNI + self.model_list.append(friend) + conversation = friend.account.get_conversation.return_value + + self.controller.open_conversation(0) + assert friend.account.method_calls == [ + ('get_conversation', (USER2_UNI,))] + assert self.tabs_controller.method_calls == [ + ('focus_conversation', (conversation,))] + + def test_accept_friendship(self): + friend = Mock() + friend.uni = USER2_UNI + friend.account.add_friend.side_effect = AsyncMethod() + self.model_list.append(friend) + + self.controller.accept_friendship(0) + assert friend.account.method_calls == [('add_friend', (USER2_UNI,))] + + def test_cancel_friendship(self): + friend = Mock() + friend.uni = USER2_UNI + friend.account.remove_friend.side_effect = AsyncMethod() + self.model_list.append(friend) + + self.controller.cancel_friendship(0) + assert friend.account.method_calls == [('remove_friend', (USER2_UNI,))] + + +class TestMainController(object): + @mockified('pypsyc.client.controller', ['TabsController', + 'FriendListController']) + def setup(self, TabsController, FriendListController): + self.client = Mock() + self.client.accounts = ObsList() + self.view = Mock() + self.controller = MainController(self.client, self.view) + self.TabsController = TabsController + self.FriendListController = FriendListController + + def test_main_controller(self): + tabs_controller = self.TabsController.return_value + assert self.TabsController.call_args_list == [ + ((self.client, self.view.tabs_view),)] + assert self.FriendListController.call_args_list == [ + ((self.client, self.view.friends_view, tabs_controller),)] + + def test_no_password(self): + account = self._make_account() + self.client.accounts.append(account) + callback_ph = PlaceHolder() + update_evt = Mock() + self.client.accounts.update_evt += update_evt + + account.no_password_evt() + assert self.view.method_calls == [ + ('show_password_dialog', (account.uni, account.__dict__, + callback_ph))] + callback_ph.obj() + assert account.active == True + assert update_evt.call_args_list == [((0, account),)] + assert self.client.method_calls == [('save_accounts',)] + + del self.client.accounts[0] + assert account.no_password_evt.observers == [] + + def test_connection_error(self): + account = self._make_account() + self.client.accounts.append(account) + + account.connection_error_evt(EXCEPTION) + assert self.view.method_calls == [ + ('show_conn_error', (account.uni, ERROR))] + + del self.client.accounts[0] + assert account.connection_error_evt.observers == [] + + def test_no_such_user(self): + account = self._make_account() + self.client.accounts.append(account) + + account.no_such_user_evt(EXCEPTION) + assert self.view.method_calls == [('show_no_such_user', (ERROR,))] + + del self.client.accounts[0] + assert account.no_such_user_evt.observers == [] + + def test_auth_error(self): + account = self._make_account() + self.client.accounts.append(account) + + account.auth_error_evt(EXCEPTION) + assert self.view.method_calls == [ + ('show_auth_error', (account.uni, ERROR))] + + del self.client.accounts[0] + assert account.auth_error_evt.observers == [] + + def _make_account(self, active=False): + account = Mock() + account.active = active + account.no_password_evt = Event() + account.connection_error_evt = Event() + account.no_such_user_evt = Event() + account.auth_error_evt = Event() + return account + + @mockified('pypsyc.client.controller', ['AccountsController']) + def test_open_accounts(self, AccountsController): + self.controller.open_accounts() + assert AccountsController.call_args_list == [ + ((self.client, self.view.show_accounts.return_value),)] + + @mockified('pypsyc.client.controller', ['DumpController']) + def test_open_dump(self, DumpController): + self.controller.open_dump() + assert DumpController.call_args_list == [ + ((self.client, self.view.show_dump_win.return_value),)] + + def test_open_conversation(self): + account1 = Mock() + account1.active = False + account2 = Mock() + account2.active = True + self.client.accounts = [account1, account2] + callback_ph = PlaceHolder() + conversation = account2.get_conversation.return_value + + self.controller.open_conversation() + assert self.view.method_calls == [ + ('show_open_conv_dialog', (iter_(account2.uni), callback_ph))] + + callback_ph.obj(0, SERVER1, USER2_NICK) + assert account2.method_calls == [('get_conversation', (USER2_UNI,))] + assert self.TabsController.return_value.method_calls == [ + ('focus_conversation', (conversation,))] + + def test_open_conference(self): + account1 = Mock() + account1.active = False + account2 = Mock() + account2.active = True + self.client.accounts = [account1, account2] + callback_ph = PlaceHolder() + conference = account2.get_conference.return_value + + self.controller.open_conference() + assert self.view.method_calls == [ + ('show_open_conf_dialog', (iter_(account2.uni), callback_ph))] + + callback_ph.obj(0, SERVER1, 'place') + assert account2.method_calls == [ + ('get_conference', (PLACE_UNI,), {'subscribe': True})] + assert self.TabsController.return_value.method_calls == [ + ('focus_conversation', (conference,))] + + def test_add_friend(self): + account1 = Mock() + account1.active = False + account2 = Mock() + account2.active = True + account2.add_friend.side_effect = AsyncMethod() + callback_ph = PlaceHolder() + self.client.accounts = [account1, account2] + + self.controller.add_friend() + assert self.view.method_calls == [ + ('show_add_friend_dialog', (iter_(account2.uni), callback_ph))] + + callback_ph.obj(0, SERVER1, USER2_NICK) + assert account2.method_calls == [('add_friend', (USER2_UNI,))] + + def test_show_active_accounts(self): + account1 = self._make_account(active=True) + account2 = self._make_account(active=True) + + self.client.accounts.append(account1) + assert self.view.method_calls == [('show_active_accounts', (True,))] + self.view.reset_mock() + + self.client.accounts.append(account2) + assert self.view.method_calls == [] + + self.client.accounts.remove(account1) + assert self.view.method_calls == [] + + self.client.accounts.remove(account2) + assert self.view.method_calls == [('show_active_accounts', (False,))] + + def test_show_active_accounts_updated(self): + account = self._make_account(active=False) + + self.client.accounts.append(account) + assert self.view.method_calls == [] + + account.active = True + self.client.accounts.updated_item(account) + assert self.view.method_calls == [('show_active_accounts', (True,))] + self.view.reset_mock() + + self.client.accounts.updated_item(account) + assert self.view.method_calls == [] + + account.active = False + self.client.accounts.updated_item(account) + assert self.view.method_calls == [('show_active_accounts', (False,))] + self.view.reset_mock() + + self.client.accounts.updated_item(account) + assert self.view.method_calls == [] + + del self.client.accounts[0] + assert self.view.method_calls == [] + + def test_quit(self): + self.controller.quit() + assert _check_obs_list(self.client.accounts) + assert self.client.method_calls == [('quit',)] diff --git a/mjacob2/tests/test_client/test_model.py b/mjacob2/tests/test_client/test_model.py new file mode 100644 index 0000000..5b04f44 --- /dev/null +++ b/mjacob2/tests/test_client/test_model.py @@ -0,0 +1,563 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +from os import remove +from tempfile import NamedTemporaryFile + +from mock import Mock, MagicMock, sentinel +from nose.tools import assert_raises +from tests.constants import (SERVER1, SERVER2, USER1_UNI, USER2_UNI, + USER1_NICK, USER2_NICK, RESOURCE, RESOURCE1_UNI, PLACE_UNI, VARIABLES, + CONTENT, PASSWORD, MESSAGE, ONLINE, PRESENCE_UNKNOWN, PRESENCE_ONLINE, + OFFERED, ESTABLISHED, ERROR, EXCEPTION) +from tests.helpers import mockified, PlaceHolder, inited_header, AsyncMethod + +from pypsyc.client.model import (Conversation, Member, Conference, Messaging, + Conferencing, Account, FriendList, ClientCircuit, Circuit, Client) +from pypsyc.core.psyc import PSYCPacket +from pypsyc.protocol import (UnknownTargetError, DeliveryFailedError, + AuthenticationError) + + +class TestConversation(object): + def test_send_message(self): + message_ph = PlaceHolder() + messaging = Mock() + conversation = Conversation(messaging, USER2_UNI) + + conversation.send_message(MESSAGE) + assert messaging.protocol.method_calls == [ + ('send_private_message', (USER2_UNI, MESSAGE), + {'relay': messaging.account.uni})] + assert conversation.messages == [message_ph] + assert message_ph.obj.source == messaging.account.uni + + +class TestConference(object): + def test_send_message(self): + conferencing = Mock() + conference = Conference(conferencing, PLACE_UNI) + + conference.send_message(MESSAGE) + assert conferencing.protocol.method_calls == [ + ('send_public_message', (PLACE_UNI, MESSAGE))] + + +class TestMessaging(object): + def test_recv_message(self): + account = Mock() + conversation = account.get_conversation.return_value + conversation.messages = [] + message_ph = PlaceHolder() + messaging = Messaging(account) + + messaging.private_message(USER2_UNI, MESSAGE) + assert account.get_conversation.call_args_list == [((USER2_UNI,),)] + assert conversation.messages == [message_ph] + assert message_ph.obj.source == USER2_UNI + assert message_ph.obj.message == MESSAGE + + @mockified('pypsyc.client.model', ['MessagingProtocol']) + def test_connected(self, MessagingProtocol): + circuit = Mock() + messaging = Messaging(Mock()) + + messaging.connected(circuit) + assert circuit.psyc.method_calls == [ + ('add_handler', (MessagingProtocol.return_value,))] + + def test_disconnected(self): + Messaging(Mock).disconnected() + + +class TestConferencing(object): + def test_member_entered(self): + account = Mock() + conference = account.get_conference.return_value + conference.members = [] + conferencing = Conferencing(account) + + conferencing.member_entered(PLACE_UNI, USER2_UNI, USER2_NICK) + assert account.get_conference.call_args_list == [((PLACE_UNI,),)] + assert conference.members == [Member(USER2_UNI, USER2_NICK)] + + def test_member_left(self): + account = Mock() + conference = account.get_conference.return_value + conference.members = [Member(USER1_UNI, USER1_NICK), + Member(USER2_UNI, USER2_NICK)] + conferencing = Conferencing(account) + + conferencing.member_left(PLACE_UNI, USER2_UNI) + assert conference.members == [Member(USER1_UNI, USER1_NICK)] + + def test_recv_message(self): + account = Mock() + conference = account.get_conference.return_value + conference.messages = [] + message_ph = PlaceHolder() + conferencing = Conferencing(account) + + conferencing.public_message(PLACE_UNI, USER2_UNI, MESSAGE) + assert account.get_conference.call_args_list == [((PLACE_UNI,),)] + assert conference.messages == [message_ph] + assert message_ph.obj.source == USER2_UNI + assert message_ph.obj.message == MESSAGE + + @mockified('pypsyc.client.model', ['ConferencingProtocol']) + def test_connected(self, ConferencingProtocol): + circuit = Mock() + conferencing = Conferencing(Mock()) + + conferencing.connected(circuit) + assert circuit.method_calls == [ + ('psyc.add_handler', (ConferencingProtocol.return_value,)), + ('add_pvar_handler', (ConferencingProtocol.return_value,))] + + def test_disconnected(self): + Conferencing(Mock()).disconnected() + + +class _Dict(dict): + def __init__(self, *args, **kwds): + dict.__init__(self, *args, **kwds) + self.updated_item = Mock() + +class TestFriendList(object): + def test_friendship(self): + friend_ph = PlaceHolder() + account = Mock() + account.friends = _Dict() + friend_list = FriendList(account) + + friend_list.friendship(USER1_UNI, OFFERED) + assert account.friends == {USER1_UNI: friend_ph} + friend = friend_ph.obj + assert friend.account == account + assert friend.uni == USER1_UNI + assert friend.presence == PRESENCE_UNKNOWN + assert friend.state == OFFERED + + friend_list.friendship(USER1_UNI, ESTABLISHED) + assert friend.state == ESTABLISHED + assert account.friends.updated_item.call_args_list == [((USER1_UNI,),)] + + friend_list.friendship_removed(USER1_UNI) + assert account.friends == {} + + def test_presence(self): + account = Mock() + account.friends = _Dict({USER2_UNI: Mock()}) + friend_list = FriendList(account) + + friend_list.presence(USER2_UNI, ONLINE) + assert account.friends[USER2_UNI].presence == PRESENCE_ONLINE + assert account.friends.updated_item.call_args_list == [((USER2_UNI,),)] + + @mockified('pypsyc.client.model', ['FriendshipProtocol']) + def test_connected(self, FriendshipProtocol): + circuit = Mock() + friend_list = FriendList(Mock()) + + friend_list.connected(circuit) + assert circuit.method_calls == [ + ('psyc.add_handler', (FriendshipProtocol.return_value,)), + ('add_pvar_handler', (FriendshipProtocol.return_value,))] + + def test_disconnected(self): + account = Mock() + account.friends = _Dict({USER2_UNI: Mock()}) + friend_list = FriendList(account) + + friend_list.disconnected() + assert account.friends == {} + + +class TestAccount(object): + @mockified('pypsyc.client.model', ['ObsDict']) + def test_account(self, ObsDict): + obs_dict = ObsDict.return_value = MagicMock() + account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False) + assert account.uni == USER1_UNI + assert ObsDict.call_args_list == [(), (), ()] + assert account.conversations == obs_dict + assert account.conferences == obs_dict + assert account.friends == obs_dict + + @mockified('pypsyc.client.model', ['Messaging', 'Conversation']) + def test_get_conversation(self, Messaging, Conversation): + messaging = Messaging.return_value + conversation = Conversation.return_value + account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False) + assert Messaging.call_args_list == [((account,),)] + + assert account.get_conversation(USER2_UNI) == conversation + assert account.get_conversation(USER2_UNI) == conversation + assert Conversation.call_args_list == [((messaging, USER2_UNI),)] + assert account.conversations == {USER2_UNI: conversation} + + @mockified('pypsyc.client.model', ['Conferencing', 'Conference']) + def test_get_conference(self, Conferencing, Conference): + conferencing = Conferencing.return_value + conference = Conference.return_value + account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False) + assert Conferencing.call_args_list == [((account,),)] + + assert account.get_conference(PLACE_UNI) == conference + assert account.get_conference(PLACE_UNI) == conference + assert Conference.call_args_list == [((conferencing, PLACE_UNI),)] + assert account.conferences == {PLACE_UNI: conference} + + @mockified('pypsyc.client.model', ['Conferencing', 'Conference']) + def test_get_conference_subscribe(self, Conferencing, Conference): + conferencing = Conferencing.return_value + conference = Conference.return_value + account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False) + client_interface = account.client_interface = Mock() + client_interface.subscribe.side_effect = AsyncMethod() + + assert account.get_conference(PLACE_UNI, subscribe=True) == conference + assert account.get_conference(PLACE_UNI, subscribe=True) == conference + assert Conference.call_args_list == [((conferencing, PLACE_UNI),)] + assert account.conferences == {PLACE_UNI: conference} + assert client_interface.method_calls == [('subscribe', (PLACE_UNI,))] + + @mockified('pypsyc.client.model', ['Conference']) + def test_get_conference_subscribe_unknown_target(self, Conference): + conference = Conference.return_value + account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False) + client_interface = account.client_interface = Mock() + client_interface.subscribe.side_effect = UnknownTargetError + + account.get_conference(PLACE_UNI, subscribe=True) + assert conference.method_calls == [('unknown_target_evt',)] + + @mockified('pypsyc.client.model', ['Conference']) + def test_get_conference_subscribe_delivery_failed(self, Conference): + conference = Conference.return_value + account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False) + client_interface = account.client_interface = Mock() + client_interface.subscribe.side_effect = DeliveryFailedError(ERROR) + + account.get_conference(PLACE_UNI, subscribe=True) + assert conference.method_calls == [('delivery_failed_evt', (ERROR,))] + + def test_remove_conference(self): + account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False) + account.conferences[PLACE_UNI] = Mock() + client_interface = account.client_interface = Mock() + client_interface.unsubscribe.side_effect = AsyncMethod() + + del account.conferences[PLACE_UNI] + assert client_interface.method_calls == [('unsubscribe', (PLACE_UNI,))] + + def test_add_friend(self): + account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False) + client_interface = account.client_interface = Mock() + + account.add_friend(USER2_UNI) + assert client_interface.method_calls == [('add_friend', (USER2_UNI,))] + + def test_remove_friend(self): + account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False) + client_interface = account.client_interface = Mock() + + account.remove_friend(USER2_UNI) + assert client_interface.method_calls == [ + ('remove_friend', (USER2_UNI,))] + + @mockified('pypsyc.client.model', ['FriendList']) + def test_friendlist(self, FriendList): + account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False) + assert FriendList.call_args_list == [((account,),)] + + def test_unknown_target_error(self): + account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False) + account.conversations[USER1_UNI] = conversation = Mock() + account.conferences[PLACE_UNI] = conference = Mock() + + account.unknown_target_error(USER1_UNI) + account.unknown_target_error(USER2_UNI) + account.unknown_target_error(PLACE_UNI) + assert conversation.method_calls == [('unknown_target_evt',)] + assert conference.method_calls == [('unknown_target_evt',)] + + def test_delivery_failed_error(self): + account = Account(None, SERVER1, USER1_NICK, PASSWORD, True, False) + account.conversations[USER1_UNI] = conversation = Mock() + account.conferences[PLACE_UNI] = conference = Mock() + + account.delivery_failed_error(USER1_UNI, ERROR) + account.delivery_failed_error(USER2_UNI, ERROR) + account.delivery_failed_error(PLACE_UNI, ERROR) + assert conversation.method_calls == [('delivery_failed_evt', (ERROR,))] + assert conference.method_calls == [('delivery_failed_evt', (ERROR,))] + + @mockified('pypsyc.client.model', ['resolve_hostname', 'connect', + 'LinkingProtocol', + 'RoutingErrorProtocol', + 'ClientInterfaceProtocol', 'Messaging', + 'Conferencing', 'FriendList']) + def test_active(self, resolve_hostname, connect, LinkingProtocol, + RoutingErrorProtocol, ClientInterfaceProtocol, Messaging, + Conferencing, FriendList): + resolve_hostname.return_value = (sentinel.ip, sentinel.port) + circuit = connect.return_value + linking_protocol = LinkingProtocol.return_value + linked = linking_protocol.link.side_effect = AsyncMethod() + messaging = Messaging.return_value + conferencing = Conferencing.return_value + friend_list = FriendList.return_value + client = Mock() + account = Account(client, SERVER1, USER1_NICK, PASSWORD, True, False) + + account.active = True + account.active = True + assert resolve_hostname.call_args_list == [((account.server,),)] + assert connect.call_args_list == [ + ((sentinel.ip, sentinel.port, client),)] + assert LinkingProtocol.call_args_list == [((circuit,),)] + assert linking_protocol.method_calls == [ + ('link', (USER1_UNI, RESOURCE, PASSWORD))] + + linked.callback() + assert circuit.psyc.uni == RESOURCE1_UNI + assert RoutingErrorProtocol.call_args_list == [((account,),)] + assert circuit.psyc.method_calls == [ + ('add_handler', (RoutingErrorProtocol.return_value,))] + assert ClientInterfaceProtocol.call_args_list == [((account,),)] + assert messaging.method_calls == [('connected', (circuit,))] + assert conferencing.method_calls == [('connected', (circuit,))] + assert friend_list.method_calls == [('connected', (circuit,))] + messaging.reset_mock() + conferencing.reset_mock() + friend_list.reset_mock() + + account.active = False + account.active = False + assert circuit.transport.method_calls == [('loseConnection',)] + assert not hasattr(account, 'circuit') + assert messaging.method_calls == [('disconnected',)] + assert conferencing.method_calls == [('disconnected',)] + assert friend_list.method_calls == [('disconnected',)] + + @mockified('pypsyc.client.model', ['resolve_hostname']) + def test_active_no_password(self, resolve_hostname): + no_password_evt = Mock() + account = Account(None, SERVER1, USER1_NICK, '', False, False) + account.no_password_evt += no_password_evt + + account.active = True + assert account.active == False + assert no_password_evt.call_args_list == [()] + assert resolve_hostname.call_args_list == [] + + @mockified('pypsyc.client.model', ['resolve_hostname', 'connect']) + def test_active_dns_error(self, resolve_hostname, connect): + resolve_hostname.side_effect = EXCEPTION + client = Mock() + connection_error_evt = Mock() + account = Account(client, SERVER1, USER1_NICK, PASSWORD, False, False) + account.connection_error_evt += connection_error_evt + + account.active = True + assert account.active == False + assert client.accounts.updated_item.call_args_list == [((account,),)] + assert connection_error_evt.call_args_list == [((EXCEPTION,),)] + assert connect.call_args_list == [] + + @mockified('pypsyc.client.model', ['resolve_hostname', 'connect', + 'LinkingProtocol']) + def test_active_connect_error(self, resolve_hostname, connect, + LinkingProtocol): + client = Mock() + resolve_hostname.return_value = (sentinel.ip, sentinel.port) + connect.side_effect = EXCEPTION + connection_error_evt = Mock() + account = Account(client, SERVER1, USER1_NICK, PASSWORD, False, False) + account.connection_error_evt += connection_error_evt + + account.active = True + assert account.active == False + assert client.accounts.updated_item.call_args_list == [((account,),)] + assert connection_error_evt.call_args_list == [((EXCEPTION,),)] + assert LinkingProtocol.call_args_list == [] + + @mockified('pypsyc.client.model', ['resolve_hostname', 'connect', + 'LinkingProtocol']) + def test_active_no_such_user(self, resolve_hostname, connect, + LinkingProtocol): + ERROR = UnknownTargetError() + client = Mock() + resolve_hostname.return_value = (sentinel.ip, sentinel.port) + circuit = connect.return_value + LinkingProtocol.return_value.link.side_effect = ERROR + no_such_user_evt = Mock() + account = Account(client, SERVER1, USER1_NICK, PASSWORD, False, False) + account.no_such_user_evt += no_such_user_evt + + account.active = True + assert account.active == False + assert client.accounts.updated_item.call_args_list == [((account,),)] + assert no_such_user_evt.call_args_list == [((ERROR,),)] + assert circuit.psyc.uni != account.uni.chain(account.resource) + + @mockified('pypsyc.client.model', ['resolve_hostname', 'connect', + 'LinkingProtocol']) + def test_active_incorrect_password(self, resolve_hostname, connect, + LinkingProtocol): + ERROR = AuthenticationError() + client = Mock() + resolve_hostname.return_value = (sentinel.ip, sentinel.port) + circuit = connect.return_value + LinkingProtocol.return_value.link.side_effect = ERROR + auth_error_evt = Mock() + account = Account(client, SERVER1, USER1_NICK, 'wrong', False, False) + account.auth_error_evt += auth_error_evt + + account.active = True + assert account.active == False + assert client.accounts.updated_item.call_args_list == [((account,),)] + assert auth_error_evt.call_args_list == [((ERROR,),)] + assert circuit.psyc.uni != account.uni.chain(account.resource) + + def test_connection_error(self): + client = Mock() + connection_error_evt = Mock() + account = Account(client, SERVER1, USER1_NICK, PASSWORD, False, False) + account.connection_error_evt += connection_error_evt + + account.connection_error(EXCEPTION) + assert account.active == False + assert client.accounts.updated_item.call_args_list == [((account,),)] + assert connection_error_evt.call_args_list == [((EXCEPTION,),)] + + +class TestClientCircuit(object): + @mockified(Circuit, ['connectionMade']) + def test_dump_out(self, connectionMade): + LINE1 = ':_target\t' + USER2_UNI + LINE2 = '|' + + cc = ClientCircuit() + dump_evt = Mock() + cc.dump_evt += dump_evt + cc.transport = Mock() + orig_write = cc.transport.write + + cc.connectionMade() + assert connectionMade.call_args_list == [((cc,),)] + + cc.send({'_target': USER2_UNI}, '') + assert dump_evt.call_args_list == [(('o', LINE1),), (('o', LINE2),)] + assert orig_write.call_args_list == [((LINE1+'\n'+LINE2+'\n',),)] + + @mockified(Circuit, ['lineReceived']) + def test_dump_in(self, lineReceived): + lineReceived.return_value = None + cc = ClientCircuit() + dump_evt = Mock() + cc.dump_evt += dump_evt + + cc.dataReceived('a\nb\n') + assert dump_evt.call_args_list == [(('i', 'a'),), (('i', 'b'),)] + assert lineReceived.call_args_list == [((cc, 'a'),), ((cc, 'b'),)] + + @mockified('pypsyc.client.model', ['PSYCObject']) + def test_packet_received(self, PSYCObject): + psyc = PSYCObject.return_value + psyc.handle_packet.return_value = PSYCPacket() + + cc = ClientCircuit() + assert PSYCObject.call_args_list == [((cc.send,),)] + + cc.packet_received({}, CONTENT) + assert psyc.method_calls == [('handle_packet', ({}, CONTENT))] + + @mockified('pypsyc.client.model', ['PSYCObject']) + def test_relay(self, PSYCObject): + psyc = PSYCObject.return_value + psyc.uni = RESOURCE1_UNI + psyc.handle_packet.return_value = PSYCPacket() + cc = ClientCircuit() + + header = inited_header(_source=USER1_UNI, _source_relay=USER2_UNI) + cc.packet_received(header, CONTENT) + assert header.source == USER2_UNI + + header2 = inited_header(_source=USER2_UNI, _source_relay=USER2_UNI) + assert_raises(AssertionError, cc.packet_received, header2, CONTENT) + + @mockified('pypsyc.client.model', ['PSYCObject']) + def test_persistent(self, PSYCObject): + HEADER = {'_context': USER2_UNI} + packet = PSYCPacket({'=': VARIABLES, '+': VARIABLES, '-': VARIABLES}) + packet.header = inited_header(HEADER) + psyc = PSYCObject.return_value + psyc.handle_packet.return_value = packet + cc = ClientCircuit() + + handler = Mock() + state_set_key = handler.state_set_key = Mock() + state_add_key = handler.state_add_key = Mock() + state_remove_key = handler.state_remove_key = Mock() + cc.add_pvar_handler(handler) + + cc.packet_received(HEADER, CONTENT) + assert psyc.method_calls == [('handle_packet', (HEADER, CONTENT))] + assert state_set_key.call_args_list == [((USER2_UNI, VARIABLES),)] + assert state_add_key.call_args_list == [((USER2_UNI, VARIABLES),)] + assert state_remove_key.call_args_list == [((USER2_UNI, VARIABLES),)] + + +class TestClient(object): + @mockified('pypsyc.client.model', ['Account']) + def test_accounts(self, Account): + account = Account.return_value + error_ph = PlaceHolder() + + account1 = Mock() + account1.server = SERVER1 + account1.person = USER1_NICK + account1.password = PASSWORD + account1.save_password = True + account1.active = False + account2 = Mock() + account2.server = SERVER2 + account2.person = USER2_NICK + account2.password = PASSWORD + account2.save_password = False + account2.active = True + + try: + with NamedTemporaryFile(delete=False) as f: + f.write('[]') + + client = Client(accounts_file=f.name) + assert client.accounts == [] + client.accounts = [account1, account2] + client.save_accounts() + + client = Client(accounts_file=f.name) + client.load_accounts() + assert client.accounts == [Account.return_value] * 2 + assert Account.call_args_list == [ + ((client, SERVER1, USER1_NICK, PASSWORD, True, False),), + ((client, SERVER2, USER2_NICK, '', False, True),)] + finally: + remove(f.name) + + client.connection_lost(account.circuit, sentinel.error) + assert account.method_calls == [('connection_error', (error_ph,))] * 2 + assert error_ph.obj.args[0] == str(sentinel.error) + + @mockified('pypsyc.client.model', ['reactor']) + def test_quit(self, reactor): + account = Mock() + client = Client() + client.accounts = [account] + + client.quit() + assert account.active == False + assert reactor.method_calls == [('stop',)] diff --git a/mjacob2/tests/test_client/test_observable.py b/mjacob2/tests/test_client/test_observable.py new file mode 100644 index 0000000..4d2f3bd --- /dev/null +++ b/mjacob2/tests/test_client/test_observable.py @@ -0,0 +1,173 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +from mock import Mock +from nose.tools import assert_raises + +from pypsyc.client.observable import ObsAttr, ObsObj, ObsList, ObsDict + + +class StubObsObj(ObsObj): + foo = ObsAttr('foo') + + +def test_obs_obj(): + obs_obj = StubObsObj() + update_evt = Mock() + obs_obj.update_evt['foo'] += update_evt + + obs_obj.ignore = 'foo' + assert not hasattr(obs_obj, 'foo') + obs_obj.foo = 'foo' + obs_obj.foo = 'foo' + obs_obj.foo = 'bar' + assert obs_obj.foo == 'bar' + + del obs_obj.foo + assert not hasattr(obs_obj, 'foo') + assert_raises(AttributeError, obs_obj.__delattr__, 'foo') + + assert update_evt.call_args_list == [ + ((None, 'foo'),), (('foo', 'bar'),), (('bar', None),)] + + +class TestObsList(object): + def test_basic(self): + obs_list = ObsList(xrange(10)) + setitem_evt = Mock() + obs_list.setitem_evt += setitem_evt + delitem_evt = Mock() + obs_list.delitem_evt += delitem_evt + insert_evt = Mock() + obs_list.insert_evt += insert_evt + + obs_list[3] = 3 + obs_list[3] = 333 + del obs_list[3] + obs_list.insert(3, 3) + + assert obs_list == list(xrange(10)) + assert setitem_evt.call_args_list == [((3, 3, 333),)] + assert delitem_evt.call_args_list == [((3, 333),)] + assert insert_evt.call_args_list == [((3, 3),)] + + def test_ext_add(self): + obs_list = ObsList() + insert_evt = Mock() + obs_list.insert_evt += insert_evt + + obs_list.append(42) + obs_list.extend([42, 1337]) + obs_list += [42, 1337] + + assert obs_list == [42, 42, 1337, 42, 1337] + assert insert_evt.call_args_list == [ + ((0, 42),), ((1, 42),), ((2, 1337),), ((3, 42),), ((4, 1337),)] + + def test_ext_del(self): + obs_list = ObsList(xrange(10)) + delitem_evt = Mock() + obs_list.delitem_evt += delitem_evt + + assert obs_list.pop() == 9 + assert obs_list.pop(8) == 8 + obs_list.remove(7) + + assert delitem_evt.call_args_list == [((9, 9),), ((8, 8),), ((7, 7),)] + assert obs_list == list(xrange(7)) + + def test_reverse(self): + obs_list = ObsList(xrange(10)) + obs_list.reverse() + assert obs_list == list(reversed(xrange(10))) + + def test_updated_item(self): + obs_list = ObsList(['a', 'b']) + update_evt = Mock() + obs_list.update_evt += update_evt + + obs_list.updated_item('b') + assert update_evt.call_args_list == [((1, 'b'),)] + + +class TestObsDict(object): + def test_basic(self): + obs_dict = ObsDict(a='A') + setitem_evt = Mock() + obs_dict.setitem_evt += setitem_evt + delitem_evt = Mock() + obs_dict.delitem_evt += delitem_evt + + obs_dict['a'] = 'A' + obs_dict['a'] = 'X' + del obs_dict['a'] + obs_dict['b'] = 'B' + + assert obs_dict == dict(b='B') + assert setitem_evt.call_args_list == [(('a', 'A', 'X'),), + (('b', None, 'B'),)] + assert delitem_evt.call_args_list == [(('a', 'X'),)] + + def test_pop(self): + obs_dict = ObsDict(a='A', b='B') + delitem_evt = Mock() + obs_dict.delitem_evt += delitem_evt + + assert obs_dict.pop('a') == 'A' + assert obs_dict.pop('a', 'X') == 'X' + assert obs_dict.pop('b', 'X') == 'B' + + assert obs_dict == {} + assert delitem_evt.call_args_list == [(('a', 'A'),), (('b', 'B'),)] + + def test_popitem(self): + obs_dict = ObsDict(a='A', b='B') + delitem_evt = Mock() + obs_dict.delitem_evt += delitem_evt + + k, v = obs_dict.popitem() + assert v == k.upper() + + assert len(obs_dict) == 1 + assert delitem_evt.call_args_list == [((k, v),)] + + def test_clear(self): + obs_dict = ObsDict(a='A', b='B') + delitem_evt = Mock() + obs_dict.delitem_evt += delitem_evt + + obs_dict.clear() + + assert delitem_evt.call_args_list == [(('a', 'A'),), (('b', 'B'),)] + + def test_update(self): + obs_dict = ObsDict() + setitem_evt = Mock() + obs_dict.setitem_evt += setitem_evt + + obs_dict.update({'a': 'A'}, b='B') + obs_dict.update((('c', 'C'),)) + + assert obs_dict == dict(a='A', b='B', c='C') + assert setitem_evt.call_args_list == [ + (('a', None, 'A'),), (('b', None, 'B'),), (('c', None, 'C'),)] + + def test_setdefault(self): + obs_dict = ObsDict(a='A') + setitem_evt = Mock() + obs_dict.setitem_evt += setitem_evt + + assert obs_dict.setdefault('a') == 'A' + assert obs_dict.setdefault('b', 'B') == 'B' + + assert obs_dict == dict(a='A', b='B') + assert setitem_evt.call_args_list == [(('b', None, 'B'),)] + + def test_updated_item(self): + obs_dict = ObsDict({'a': 'A', 'b': 'B'}) + update_evt = Mock() + obs_dict.update_evt += update_evt + + obs_dict.updated_item('b') + assert update_evt.call_args_list == [(('b', 'B'),)] diff --git a/mjacob2/tests/test_client/test_view.py b/mjacob2/tests/test_client/test_view.py new file mode 100644 index 0000000..bfc1bee --- /dev/null +++ b/mjacob2/tests/test_client/test_view.py @@ -0,0 +1,111 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" + + +def _callback(*args, **kwds): # pragma: no cover + print ('callback', args, kwds) + +def _test_accounts_view(): # pragma: no cover + accounts_view = AccountsView() + Controller(controller.AccountsController, accounts_view) + accounts_view.window.connect('destroy', lambda w: gtk.main_quit()) + for i in xrange(10): + accounts_view.accounts.append(("psyc://server/~account%i" % i, True)) + accounts_view.accounts[4] = ("psyc://server/~updatedaccount4", False) + #return + + from pypsyc.client.model import Account + account = Account(None, "", "", "", False, False) + accounts_view.show_addedit_dialog(account.__dict__, True, _callback) + +def _test_tabs_view(): # pragma: no cover + status_icon = gtk.StatusIcon() + status_icon.set_from_file('pypsyc/client/psyc.ico') + status_icon.connect('activate', lambda w: tabs_view.on_status_icon_click()) + tabs_view = TabsView(status_icon) + tabs_view.window.connect('destroy', lambda w: gtk.main_quit()) + Controller(controller.TabsController, tabs_view) + + conversation_view = tabs_view.show_conversation("Conversation 1") + Controller(controller.ConversationController, conversation_view) + tabs_view.focus_tab(conversation_view) + + conference_view = tabs_view.show_conference("Conversation 2") + Controller(controller.ConferenceController, conference_view) + + def show(): + for i in xrange(40): + conversation_view.show_message("Message %02i" % i) + for i in xrange(40): + conference_view.members.append(("psyc://server/~person%02i" % i, + "person%02i" % i)) + conference_view.show_message("Message %02i" % i) + conference_view.members[20] = ("psyc://server/~longpersonname20", + "longpersonname20") + idle_add(show) + +def _test_dump_view(): # pragma: no cover + dump_view = DumpView() + Controller(controller.DumpController, dump_view) + dump_view.show_line('o', 'a', 'psyc://server/~account') + dump_view.show_line('i', ':_tag_relay\t\xc8\xe6', 'psyc://server/~account') + +def _test_main_view(): # pragma: no cover + main_view = MainView() + Controller(controller.MainController, main_view) + Controller(controller.FriendListController, main_view.friends_view) + for i in xrange(40): + main_view.friends_view.friends.append(("Friend %02i" % i, False, + 'pending')) + for i in xrange(0, 40, 2): + main_view.friends_view.friends[i] = ("Friend %02i updated" % i, False, + 'offered') + for i in xrange(0, 40, 3): + main_view.friends_view.friends[i] = ("Friend %02i updated 2" % i, True, + 'established') + #return + + account_dict = {'password': '', 'save_password': False} + main_view.show_password_dialog("psyc://server/~account", account_dict, + _callback) + main_view.show_no_such_user("psyc://server/~account") + main_view.show_auth_error("psyc://server/~account", "Error description") + main_view.show_open_conv_dialog(["psyc://server/~person1", + "psyc://server/~person2"], _callback) + main_view.show_open_conf_dialog(["psyc://server/~person1", + "psyc://server/~person2"], _callback) + main_view.show_add_friend_dialog(["psyc://server/~person1", + "psyc://server/~person2"], _callback) + + +if __name__ == '__main__': # pragma: no cover + class Controller(object): + def __init__(self, controller, view): + self.controller = controller + self.c_name = controller.__name__ + view.controller = self + def __getattr__(self, attr): + m = getattr(self.controller, attr, None) + assert m, "%s has no method %s" % (self.c_name, attr) + def f(*args, **kwds): + m_argcount = m.__code__.co_argcount - 1 # minus self + f_argcount = len(args) + len(kwds) + assert m_argcount == f_argcount, \ + "%s.%s has %s arguments, called with %s arguments" % ( + self.c_name, attr, m_argcount, f_argcount) + print "%s.%s called: %s, %s" % (self.c_name, attr, args, kwds) + return f + + from gobject import idle_add + import gtk + + from pypsyc.client.view import AccountsView, TabsView, DumpView, MainView + from pypsyc.client import controller + +# _test_accounts_view() +# _test_tabs_view() +# _test_dump_view() +# _test_main_view() + gtk.main() diff --git a/mjacob2/tests/test_core/test_mmp.py b/mjacob2/tests/test_core/test_mmp.py new file mode 100644 index 0000000..facbba3 --- /dev/null +++ b/mjacob2/tests/test_core/test_mmp.py @@ -0,0 +1,97 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +from nose.tools import assert_raises +from twisted.protocols.loopback import loopbackAsync as loopback_async + +from pypsyc.core.mmp import Uni, Header, Circuit + + +UNI = 'psyc://server/foo/bar' + +class TestUni(object): + def test_uni(self): + uni = Uni(UNI) + assert uni == UNI + assert uni.prefix == 'psyc' + + def test_parts(self): + uni = Uni(UNI) + assert uni.into_parts() == ['server', 'foo', 'bar'] + assert Uni.from_parts(uni.into_parts()) == uni + + def test_descendant_ancestor(self): + uni = Uni(UNI) + assert uni.is_descendant_of('psyc://server/foo') + assert not uni.is_descendant_of('psyc://server/spam') + assert uni.is_ancestor_of('psyc://server/foo/bar/child') + assert not uni.is_ancestor_of('psyc://server/spam/eggs/child') + + assert uni.is_descendant_of('psyc://server/') + assert Uni('psyc://server/').is_ancestor_of('psyc://server/foo/bar') + + def test_chain(self): + assert Uni(UNI).chain('foobar') == 'psyc://server/foo/bar/foobar' + assert Uni('psyc://server/').chain('foo') == 'psyc://server/foo' + + +class TestHeader(object): + def test_empty(self): + header = Header() + assert header == {} + header._init() + assert header.source is None + assert header.target is None + assert header.context is None + + def test_header(self): + header_dict = { + '_source': 'psyc://server/uni1', + '_target': 'psyc://server/uni2', + '_context': 'psyc://server/@place' + } + header = Header(header_dict) + assert header == header_dict + header._init() + assert header.source == Uni('psyc://server/uni1') + assert header.target == Uni('psyc://server/uni2') + assert header.context == Uni('psyc://server/@place') + + +class TestCircuit(object): + def setup(self): + self.success = False + + def test_render_empty(self): + self._test({}, []) + + def test_onlyvars(self): + self._test({'_target': 'psyc://example.org/'}, []) + + def test_onlycontent(self): + self._test({}, ['content']) + + def test_packet(self): + self._test({'_target': 'psyc://example.org/'}, ['content']) + + def _test(self, header, content): + def received(header_, content_): + assert header_ == header + assert content_ == content + self.success = True + + circuit1 = Circuit() + circuit1.packet_received = received + + circuit2 = Circuit() + circuit2.initiator = True + circuit2.inited = lambda: circuit2.send(header, iter(content)) + + loopback_async(circuit1, circuit2) + assert self.success + + def test_subclass(self): + circuit = Circuit() + circuit.lineReceived('|') + assert_raises(NotImplementedError, circuit.lineReceived, '|') diff --git a/mjacob2/tests/test_core/test_psyc.py b/mjacob2/tests/test_core/test_psyc.py new file mode 100644 index 0000000..be544c0 --- /dev/null +++ b/mjacob2/tests/test_core/test_psyc.py @@ -0,0 +1,151 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +from mock import Mock +from nose.tools import assert_raises +from tests.constants import USER1_UNI, USER2_UNI +from tests.helpers import check_success, rendered + +from pypsyc.core.psyc import PSYCPacket, PSYCObject +from pypsyc.util import schedule + + +class TestPSYCPacket(object): + def test_vars(self): + packet = PSYCPacket( + modifiers={ + ':': {'_foo': 'bar'}, + '=': {'_spam': 'eggs'} + } + ) + self._test(packet, [':_foo\tbar', '=_spam\teggs']) + + def test_method(self): + packet = PSYCPacket(mc='_notice_foo') + self._test(packet, ['_notice_foo']) + + def test_packet(self): + packet = PSYCPacket( + modifiers={ + ':': {'_foo': 'bar'} + }, + mc='_message_foo', + data='this is\ndata' + ) + self._test(packet, [':_foo\tbar', '_message_foo', 'this is\ndata']) + + def test_binary1(self): + packet = PSYCPacket({':': {'_foo': 'foo\nbar'}}) + self._test(packet, [':_foo 7\tfoo\nbar']) + assert PSYCPacket.parse([':_foo 7\tfoo', 'bar']) == packet + + def test_binary2(self): + packet = PSYCPacket({':': {'_foo': 'foo\n\nbar'}}) + self._test(packet, [':_foo 8\tfoo\n\nbar']) + assert PSYCPacket.parse([':_foo 8\tfoo', '', 'bar']) == packet + + def _test(self, packet, rendered_): + assert PSYCPacket.parse(packet.render()) == packet + assert rendered(packet) == rendered_ + + def test_nomethod(self): + packet = PSYCPacket(data='foobar') + assert_raises(AssertionError, list, packet.render()) + + def test_repr(self): + s = '\n'.join(( + r"PSYCPacket(", + r" modifiers={", + r" ':': {'_foo': 'bar'},", + r" '=': {'_foo': 'bar'}", + r" },", + r" mc='_message_foo',", + r" data='this is\ndata'", + r")" + )) + assert repr(eval(s)) == s + + def test_equal(self): + assert PSYCPacket() == PSYCPacket() + assert not PSYCPacket() != PSYCPacket() + + def test_empty(self): + packet = PSYCPacket() + self._test(packet, []) + assert packet.modifiers == {':': {}} + assert packet.mc is None + assert packet.data == '' + + def test_from_kwds(self): + packet1 = PSYCPacket.from_kwds(mc='_message_public', + data="hello", _somevar='foo') + packet2 = PSYCPacket(mc='_message_public', data="hello", + modifiers={':': {'_somevar': 'foo'}}) + assert packet1 == packet2 + + +class TestPSYCObject(object): + def test_handle_packet(self): + psyc = PSYCObject(None) + psyc.handlers['_message'] = message = Mock() + psyc.handlers['_message_public'] = message_public = Mock() + + p1 = PSYCPacket() # psyc_packet should return + p2 = PSYCPacket(mc='_message_public') + p3 = PSYCPacket(mc='_message_private') + + assert psyc.handle_packet({}, p1.render()) == p1 + assert psyc.handle_packet({}, p2.render()) == p2 + assert psyc.handle_packet({}, p3.render()) == p3 + assert message_public.call_args_list == [((p2,),)] + assert message.call_args_list == [((p3,),)] + + def test_handle_packet_internal_error(self): + psyc = PSYCObject(None) + psyc.handlers['_message_public'] = Mock(side_effect=Exception) + + packet = PSYCPacket(mc='_message_public') + assert psyc.handle_packet({}, packet.render()) == packet + + def test_sendmsg_packet(self): + packet = PSYCPacket() + sendfunc = Mock() + psyc = PSYCObject(sendfunc, USER1_UNI) + + psyc.sendmsg(USER2_UNI, packet) + header = {'_source': USER1_UNI, '_target': USER2_UNI} + assert sendfunc.call_args_list == [((header, rendered(packet)),)] + + def test_sendmsg_kwds(self): + packet = PSYCPacket({':': {'_somevar': 'foo'}}, mc='_message_private') + sendfunc = Mock() + psyc = PSYCObject(sendfunc, USER1_UNI) + + psyc.sendmsg(USER2_UNI, mc='_message_private', _somevar='foo') + header = {'_source': USER1_UNI, '_target': USER2_UNI} + assert sendfunc.call_args_list == [((header, rendered(packet)),)] + + @check_success + def test_async(self): + MC = '_request_foo' + ECHO = PSYCPacket(mc='_echo_foo') + o1 = PSYCObject(lambda *args: o2.handle_packet(*args), USER1_UNI) + o2 = PSYCObject(lambda *args: o1.handle_packet(*args), USER2_UNI) + o2.handlers[MC] = lambda _: ECHO + + def _test(): + ret = o1.sendmsg(USER2_UNI, mc=MC) + assert ret == ECHO + self.success = True + schedule(_test) + + def test_async_internal_error(self): + MC = '_request_foo' + ERROR = PSYCPacket(mc='_error_internal') + o1 = PSYCObject(lambda *args: o2.handle_packet(*args), USER1_UNI) + o2 = PSYCObject(lambda *args: o1.handle_packet(*args), USER2_UNI) + o2.handlers[MC] = Mock(side_effect=Exception) + + ret = o1.sendmsg(USER2_UNI, mc=MC) + assert ret == ERROR diff --git a/mjacob2/tests/test_protocol.py b/mjacob2/tests/test_protocol.py new file mode 100644 index 0000000..d2e346a --- /dev/null +++ b/mjacob2/tests/test_protocol.py @@ -0,0 +1,396 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +import hmac +from hashlib import sha256 + +from mock import Mock +from nose.tools import assert_raises +from tests.constants import (SERVER1, USER1, USER2, USER1_UNI, USER2_UNI, + USER1_NICK, RESOURCE, RESOURCE1_UNI, RESOURCE2_UNI, PLACE, PLACE_UNI, + INTERFACE, VARIABLES, PASSWORD, MESSAGE, ONLINE, PENDING, OFFERED, + ESTABLISHED, ERROR) +from tests.helpers import connect_circuits, PlaceHolder + +from pypsyc.client.model import ClientCircuit +from pypsyc.core.psyc import PSYCPacket +from pypsyc.protocol import (UnknownTargetError, DeliveryFailedError, + RoutingErrorRoot, RoutingErrorRelaying, RoutingErrorClient, + AuthenticationError, LinkingServer, LinkingClient, Messaging, + MessageRelaying, ConferencingServer, ConferencingClient, EntryDeniedError, + ContextMaster, ContextSlave, UnauthorizedError, ClientInterfaceServer, + ClientInterfaceClient, FriendshipServer, FriendshipClient) +from pypsyc.server import Entity +from pypsyc.server.person import Resource +from pypsyc.server.routing import Routing + + +def _setup(): + server = Mock() + server.hostname = SERVER1 + routing = Routing(server.hostname, INTERFACE) + server.routing = routing + routing.init(_entity(server=server)) + return routing + +def _entity(*args, **kwds): + entity = Entity(*args, **kwds) + entity.package = Mock() + entity.package.entity = entity + return entity + +def _c2s_circuit(routing, link_to=None): + sc, cc = connect_circuits(routing.buildProtocol(None), ClientCircuit()) + cc.package = Mock() + cc.package.psyc = cc.psyc + if link_to is not None: + Resource(link_to, RESOURCE, sc) + cc.psyc.uni = link_to.uni.chain(RESOURCE) + return sc, cc + +def _enter(routing, entity, sc): + routing.mrouting_table[entity.uni].add(sc) + state = entity.context_master.add_member(USER1_UNI) + sc.send({'_context': entity.uni}, PSYCPacket({'=': state}).render()) + + +class TestRoutingErrorProtocol(object): + def setup(self): + routing = _setup() + self.root = routing.root + self.user1 = _entity(routing.root, USER1) + self.sc, self.client = _c2s_circuit(routing, link_to=self.user1) + + def test_unknown_target_error(self): + rer = RoutingErrorRoot(self.root.package) + self.client.psyc.add_handler(RoutingErrorClient(self.client.package)) + + rer.send_unknown_target_error(RESOURCE1_UNI, USER2_UNI) + assert self.client.package.method_calls == [ + ('unknown_target_error', (USER2_UNI,))] + + def test_relay_unknown_target_error1(self): + rer = RoutingErrorRoot(self.root.package) + self.user1.add_handler(RoutingErrorRelaying(self.user1.package)) + + rer.send_unknown_target_error(USER1_UNI, USER2_UNI) + assert self.user1.package.method_calls == [ + ('unknown_target_error', (USER2_UNI,))] + + def test_relay_unknown_target_error2(self): + rer = RoutingErrorRelaying(self.root.package) + self.client.psyc.add_handler(RoutingErrorClient(self.client.package)) + + rer.relay_unknown_target_error(RESOURCE1_UNI, USER2_UNI) + assert self.client.package.method_calls == [ + ('unknown_target_error', (USER2_UNI,))] + + def test_delivery_failed_error(self): + rer = RoutingErrorRoot(self.root.package) + self.client.psyc.add_handler(RoutingErrorClient(self.client.package)) + + rer.send_delivery_failed_error(RESOURCE1_UNI, USER2_UNI, ERROR) + assert self.client.package.method_calls == [ + ('delivery_failed_error', (USER2_UNI, ERROR))] + + def test_relay_delivery_failed_error1(self): + rer = RoutingErrorRoot(self.root.package) + self.user1.add_handler(RoutingErrorRelaying(self.user1.package)) + + rer.send_delivery_failed_error(USER1_UNI, USER2_UNI, ERROR) + assert self.user1.package.method_calls == [ + ('delivery_failed_error', (USER2_UNI, ERROR))] + + def test_relay_delivery_failed_error2(self): + rer = RoutingErrorRelaying(self.root.package) + self.client.psyc.add_handler(RoutingErrorClient(self.client.package)) + + rer.relay_delivery_failed_error(RESOURCE1_UNI, USER2_UNI, ERROR) + assert self.client.package.method_calls == [ + ('delivery_failed_error', (USER2_UNI, ERROR))] + + +class TestLinkingProtocol(object): + def setup(self): + routing = _setup() + self.user1 = _entity(routing.root, USER1) + self.sc, self.client = _c2s_circuit(routing) + + def test_linking(self): + lc = LinkingClient(self.client.package) + self.user1.add_handler(LinkingServer(self.user1.package)) + pw = PlaceHolder() + + lc.link(USER1_UNI, RESOURCE, PASSWORD) + assert self.user1.package.method_calls == [ + ('authenticate', (pw, self.sc, RESOURCE))] + assert pw.obj[0] == 'hmac' + assert pw.obj[1] == hmac.new(PASSWORD, pw.obj[2], sha256).digest() + + def test_incorrect_password(self): + lc = LinkingClient(self.client.package) + self.user1.add_handler(LinkingServer(self.user1.package)) + self.user1.package.authenticate.side_effect = AuthenticationError('xy') + + with assert_raises(AuthenticationError) as cm: + lc.link(USER1_UNI, RESOURCE, 'pw') + assert cm.exception.args == ('xy',) + + +class TestMessagingProtocol(object): + def setup(self): + routing = _setup() + self.user1 = _entity(routing.root, USER1) + self.user2 = _entity(routing.root, USER2) + + sc1, self.client1 = _c2s_circuit(routing, link_to=self.user1) + sc2, self.client2 = _c2s_circuit(routing, link_to=self.user2) + + def test_private_message(self): + msg1 = Messaging(self.client1.package) + self.client2.psyc.add_handler(Messaging(self.client2.package)) + + msg1.send_private_message(RESOURCE2_UNI, MESSAGE) + assert self.client2.package.method_calls == [ + ('private_message', (RESOURCE1_UNI, MESSAGE))] + + def test_message_relaying1(self): + msg1 = Messaging(self.client1.package) + self.user1.add_handler(MessageRelaying(self.user1.package)) + + msg1.send_private_message(USER2_UNI, MESSAGE, relay=USER1_UNI) + assert self.user1.package.method_calls == [ + ('private_message_relay', (RESOURCE1_UNI, USER2_UNI, MESSAGE))] + + def test_message_relaying2(self): + msg_relaying1 = MessageRelaying(self.user1.package) + self.user2.add_handler(MessageRelaying(self.user2.package)) + + msg_relaying1.send_private_message(USER2_UNI, MESSAGE) + assert self.user2.package.method_calls == [ + ('private_message', (USER1_UNI, MESSAGE))] + + def test_message_relaying3(self): + msg_relaying2 = MessageRelaying(self.user2.package) + self.client2.psyc.add_handler(Messaging(self.client2.package)) + + msg_relaying2.relay_private_message(USER1_UNI, RESOURCE2_UNI, MESSAGE) + assert self.client2.package.method_calls == [ + ('private_message', (USER1_UNI, MESSAGE))] + + +class TestConferencingProtocol(object): + def setup(self): + routing = self.routing = _setup() + self.place = _entity(routing.root, PLACE) + self.user1 = _entity(routing.root, USER1) + self.user2 = _entity(routing.root, USER2) + + self.sc1, self.client1 = _c2s_circuit(routing, link_to=self.user1) + self.sc2, self.client2 = _c2s_circuit(routing, link_to=self.user2) + _enter(routing, self.place, self.sc1) + + def test_cast_member(self): + con_server = ConferencingServer(self.place.package) + self.client1.add_pvar_handler(ConferencingClient(self.client1.package)) + self.client2.add_pvar_handler(ConferencingClient(self.client2.package)) + + con_server.cast_member_entered(USER1_UNI, USER1_NICK) + assert self.client1.package.method_calls == [ + ('member_entered', (PLACE_UNI, USER1_UNI, USER1_NICK))] + + _enter(self.routing, self.place, self.sc2) + assert self.client2.package.method_calls == [ + ('member_entered', (PLACE_UNI, USER1_UNI, USER1_NICK))] + self.client1.package.reset_mock() + + con_server.cast_member_left(USER1_UNI) + assert self.client1.package.method_calls == [ + ('member_left', (PLACE_UNI, USER1_UNI))] + + def test_public_message1(self): + con_client = ConferencingClient(self.client1.package) + self.place.add_handler(ConferencingServer(self.place.package)) + + con_client.send_public_message(PLACE_UNI, MESSAGE) + assert self.place.package.method_calls == [ + ('public_message', (RESOURCE1_UNI, MESSAGE))] + + def test_public_message2(self): + con_server = ConferencingServer(self.place.package) + self.client1.psyc.add_handler(ConferencingClient(self.client1.package)) + self.client2.psyc.add_handler(ConferencingClient(self.client2.package)) + + con_server.cast_public_message(USER1_UNI, MESSAGE) + assert self.client1.package.method_calls == [ + ('public_message', (PLACE_UNI, USER1_UNI, MESSAGE))] + + +class TestContextProtocol(object): + def setup(self): + routing = _setup() + self.place = _entity(routing.root, PLACE) + self.user1 = _entity(routing.root, USER1) + + def test_enter(self): + cs = ContextSlave(self.user1.package) + self.place.add_handler(ContextMaster(self.place.package)) + self.place.package.enter_request.return_value = VARIABLES + + state = cs.enter(PLACE_UNI) + assert state == VARIABLES + assert self.place.package.method_calls == [ + ('enter_request', (USER1_UNI,))] + + def test_enter_denied(self): + cs = ContextSlave(self.user1.package) + self.place.add_handler(ContextMaster(self.place.package)) + self.place.package.enter_request.side_effect = EntryDeniedError('xy') + + with assert_raises(EntryDeniedError) as cm: + cs.enter(PLACE_UNI) + assert cm.exception.args == ('xy',) + + def test_leave(self): + cs = ContextSlave(self.user1.package) + self.place.add_handler(ContextMaster(self.place.package)) + + cs.leave(PLACE_UNI) + assert self.place.package.method_calls == [ + ('leave_context', (USER1_UNI,))] + + +class TestClientInterface(object): + def setup(self): + routing = self.routing = _setup() + self.user1 = _entity(routing.root, USER1) + _, self.client = _c2s_circuit(routing, link_to=self.user1) + self.client.package.uni = USER1_UNI + + self.ci_client = ClientInterfaceClient(self.client.package) + self.user1.add_handler(ClientInterfaceServer(self.user1.package)) + + def test_unauthorized(self): + user2 = _entity(self.routing.root, USER2) + _, client2 = _c2s_circuit(self.routing, link_to=user2) + client2.package.uni = USER1_UNI + ci_client2 = ClientInterfaceClient(client2.package) + + assert_raises(UnauthorizedError, ci_client2.subscribe, PLACE_UNI) + assert self.user1.package.method_calls == [] + + def test_unknown_target_error(self): + error = UnknownTargetError('xy') + self.user1.package.client_subscribe.side_effect = error + with assert_raises(UnknownTargetError) as cm: + self.ci_client.subscribe(PLACE_UNI) + assert cm.exception.args == ('xy',) + + def test_delivery_failed_error(self): + error = DeliveryFailedError('x', 'y') + self.user1.package.client_subscribe.side_effect = error + with assert_raises(DeliveryFailedError) as cm: + self.ci_client.subscribe(PLACE_UNI) + assert cm.exception.args == ('x', 'y') + + def test_subscribe(self): + self.ci_client.subscribe(PLACE_UNI) + assert self.user1.package.method_calls == [ + ('client_subscribe', (PLACE_UNI, RESOURCE1_UNI))] + + def test_unsubscribe(self): + self.ci_client.unsubscribe(PLACE_UNI) + assert self.user1.package.method_calls == [ + ('client_unsubscribe', (PLACE_UNI, RESOURCE1_UNI))] + + def test_presence(self): + self.ci_client.cast_presence(ONLINE) + assert self.user1.package.method_calls == [ + ('client_presence', (ONLINE,))] + + def test_add_friend(self): + self.ci_client.add_friend(USER2_UNI) + assert self.user1.package.method_calls == [ + ('client_add_friend', (USER2_UNI,))] + + def test_remove_friend(self): + self.ci_client.remove_friend(USER2_UNI) + assert self.user1.package.method_calls == [ + ('client_remove_friend', (USER2_UNI,))] + + +class TestFriendshipProtocol(object): + def setup(self): + routing = _setup() + self.user1 = _entity(routing.root, USER1) + self.user2 = _entity(routing.root, USER2) + + sc1, self.client1 = _c2s_circuit(routing, link_to=self.user1) + sc2, self.client2 = _c2s_circuit(routing, link_to=self.user2) + routing.mrouting_table[USER1_UNI] = [sc2] + + def test_establish_pending(self): + friendship_server1 = FriendshipServer(self.user1.package) + self.user2.add_handler(FriendshipServer(self.user2.package)) + self.user2.package.friendship_request.return_value = PENDING + + state = friendship_server1.establish(USER2_UNI) + assert state == PENDING + assert self.user2.package.method_calls == [ + ('friendship_request', (USER1_UNI,))] + + def test_establish_established(self): + friendship_server1 = FriendshipServer(self.user1.package) + self.user2.add_handler(FriendshipServer(self.user2.package)) + self.user2.package.friendship_request.return_value = ESTABLISHED + + state = friendship_server1.establish(USER2_UNI) + assert state == ESTABLISHED + assert self.user2.package.method_calls == [ + ('friendship_request', (USER1_UNI,))] + + def test_remove(self): + friendship_server1 = FriendshipServer(self.user1.package) + self.user2.add_handler(FriendshipServer(self.user2.package)) + + friendship_server1.remove(USER2_UNI) + assert self.user2.package.method_calls == [ + ('friendship_cancel', (USER1_UNI,))] + + def test_friendships(self): + friendship_server = FriendshipServer(self.user1.package) + self.client1.psyc.add_handler(FriendshipClient(self.client1.package)) + + friendship_server.send_friendships(RESOURCE1_UNI, { + USER1_UNI: {'state': ESTABLISHED}, + USER2_UNI: {'state': OFFERED}}) + friendship_server.send_friendships(RESOURCE1_UNI, {}) + assert self.client1.package.method_calls == [ + ('friendship', (USER1_UNI, ESTABLISHED)), + ('friendship', (USER2_UNI, OFFERED))] + + def test_updated_friendship(self): + friendship_server = FriendshipServer(self.user1.package) + self.client1.psyc.add_handler(FriendshipClient(self.client1.package)) + + friendship_server.send_updated_friendship(RESOURCE1_UNI, USER1_UNI, + {'state': 'established'}) + assert self.client1.package.method_calls == [ + ('friendship', (USER1_UNI, ESTABLISHED))] + + def test_removed_friendship(self): + friendship_server = FriendshipServer(self.user1.package) + self.client1.psyc.add_handler(FriendshipClient(self.client1.package)) + + friendship_server.send_removed_friendship(RESOURCE1_UNI, USER1_UNI) + assert self.client1.package.method_calls == [ + ('friendship_removed', (USER1_UNI,))] + + def test_presence(self): + friendship_server = FriendshipServer(self.user1.package) + self.client2.add_pvar_handler(FriendshipClient(self.client2.package)) + + friendship_server.cast_presence(ONLINE) + assert self.client2.package.method_calls == [ + ('presence', (USER1_UNI, ONLINE))] diff --git a/mjacob2/tests/test_server/test_db.py b/mjacob2/tests/test_server/test_db.py new file mode 100644 index 0000000..23c7f7a --- /dev/null +++ b/mjacob2/tests/test_server/test_db.py @@ -0,0 +1,50 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +from greenlet import greenlet +from nose.tools import assert_raises +from tests.helpers import mockify, mockified, check_success + +from pypsyc.server.db import Database + + +class TestDatabase: + def test_sync(self): + db = Database(':memory:') + db.execute('CREATE TABLE test(test TEXT)') + db.execute("INSERT INTO test VALUES ('test')") + + result = db.fetch('SELECT test FROM test') + assert result == [('test',)] + assert isinstance(result[0][0], str) + + db.stop() + + @check_success + @mockified('pypsyc.server.db',['sqlite3', 'Thread', 'reactor']) + def test_async(self, sqlite3, Thread, reactor): + db = Database('') + mockify(db, ['_execute', '_fetch']) + e = Exception() + db._execute.side_effect = e + + def f(): + assert_raises(Exception, db.execute) + assert db.fetch() == db._fetch.return_value + self.success = True + gl = greenlet(f) + gl.switch() + + # emulate reactor calling this methods + gl.throw(e) + gl.switch(db._fetch.return_value) + + db.stop() + assert db.thread.method_calls == [('start',), ('join',)] + + db.run_async() + assert sqlite3.method_calls == [('connect', ('',))] + assert reactor.method_calls == [ + ('callFromThread', (gl.throw, e)), + ('callFromThread', (gl.switch, db._fetch.return_value))] diff --git a/mjacob2/tests/test_server/test_multicast.py b/mjacob2/tests/test_server/test_multicast.py new file mode 100644 index 0000000..199bd94 --- /dev/null +++ b/mjacob2/tests/test_server/test_multicast.py @@ -0,0 +1,162 @@ +""" + ::copyright: 2010 by Manuel Jacob + :license: MIT +""" +from mock import Mock +from tests.constants import (SERVER1, SERVER2, SERVER1_UNI, SERVER2_UNI, USER1, USER2, + USER1_UNI, USER2_UNI, RESOURCE, PLACE_UNI, VARIABLES) +from tests.helpers import mockified, rendered + +from pypsyc.core.psyc import PSYCPacket +from pypsyc.server import Entity +from pypsyc.server.multicast import ContextSlave +from pypsyc.server.person import Resource + + +EXTERN_USER_UNI = SERVER2_UNI.chain(USER1) +SET_PACKET = PSYCPacket({'=': VARIABLES}) +ADD_PACKET = PSYCPacket({'+': VARIABLES}) +REMOVE_PACKET = PSYCPacket({'-': VARIABLES}) + +class TestContextMaster(object): + def setup(self): + self.server = Mock() + self.server.hostname = SERVER1 + self.server.routing.mrouting_table = self.mrouting_table = {} + root = Entity(server=self.server) + self.user1 = Entity(root, USER1) + self.user1.castmsg = Mock() + user2 = Entity(root, USER2) + Resource(user2, RESOURCE, Mock()) + + def test_extern_member(self): + circuit = Mock() + self.server.routing.srouting_table = {SERVER2: circuit} + + self.user1.context_master.add_member(EXTERN_USER_UNI) + assert self.mrouting_table[USER1_UNI] == set([circuit]) + + self.user1.context_master.remove_member(EXTERN_USER_UNI) + assert self.mrouting_table[USER1_UNI] == set() + + def test_set_persistent(self): + self.user1.context_master.state_set(**VARIABLES) + assert self.user1.castmsg.call_args_list == [((SET_PACKET,),),] + + state = self.user1.context_master.add_member(USER2_UNI) + assert state == VARIABLES + + def test_persistent_list(self): + self.user1.context_master.state_add(**VARIABLES) + assert self.user1.castmsg.call_args_list == [((ADD_PACKET,),)] + self.user1.castmsg.reset_mock() + + state = self.user1.context_master.add_member(USER2_UNI) + assert state == VARIABLES + + self.user1.context_master.state_remove(**VARIABLES) + assert self.user1.castmsg.call_args_list == [((REMOVE_PACKET,),)] + + state = self.user1.context_master.add_member(USER2_UNI) + assert state == {} + + def test_remove_inheritance(self): + self.user1.context_master.state_add(_a='a', _a_b='b') + self.user1.context_master.state_remove(_a='a') + + state = self.user1.context_master.add_member(USER2_UNI) + assert state == {} + + +RESOURCE1 = '*resource1' +RESOURCE2 = '*resource2' +USER1_RESOURCE1_UNI = USER1_UNI.chain(RESOURCE1) +USER1_RESOURCE2_UNI = USER1_UNI.chain(RESOURCE2) + +class TestContextSlave(object): + @mockified('pypsyc.server.multicast', ['ContextSlaveProtocol']) + def setup(self, ContextSlaveProtocol): + person = Mock() + self.entity = person.entity + self.entity._root.uni = SERVER1_UNI + self.entity.uni = USER1_UNI + self.entity.server.routing.mrouting_table = self.mrouting_table = {} + self.entity.children = {} + self.entity.children[RESOURCE1] = self.resource1 = Mock() + self.entity.children[RESOURCE2] = self.resource2 = Mock() + self.protocol = ContextSlaveProtocol.return_value + self.protocol.enter.return_value = VARIABLES + self.context_slave = ContextSlave(person) + assert ContextSlaveProtocol.call_args_list == [((person,),)] + + def test_enter_leave(self): + self.context_slave.enter(PLACE_UNI, USER1_RESOURCE1_UNI) + assert self.protocol.method_calls == [('enter', (PLACE_UNI,))] + assert self.mrouting_table[PLACE_UNI] == set([self.resource1.circuit]) + assert self.resource1.circuit.method_calls == [ + ('send', ({'_context': PLACE_UNI}, rendered(SET_PACKET)))] + self.protocol.reset_mock() + + self.context_slave.leave(PLACE_UNI, USER1_RESOURCE1_UNI) + assert self.protocol.method_calls == [('leave', (PLACE_UNI,))] + assert self.mrouting_table[PLACE_UNI] == set() + + def test_enter_leave_all_resources(self): + self.context_slave.enter(PLACE_UNI) + assert self.protocol.method_calls == [('enter', (PLACE_UNI,))] + assert self.mrouting_table[PLACE_UNI] == set([self.resource1.circuit, + self.resource2.circuit]) + assert self.resource1.circuit.method_calls == [ + ('send', ({'_context': PLACE_UNI}, rendered(SET_PACKET)),)] + assert self.resource2.circuit.method_calls == [ + ('send', ({'_context': PLACE_UNI}, rendered(SET_PACKET)),)] + self.protocol.reset_mock() + + self.context_slave.leave(PLACE_UNI) + assert self.protocol.method_calls == [('leave', (PLACE_UNI,))] + assert self.mrouting_table[PLACE_UNI] == set() + + def test_enter_leave_two_resources(self): + extern_circuit = Mock() + self.mrouting_table[PLACE_UNI] = set([extern_circuit]) + + self.context_slave.enter(PLACE_UNI, USER1_RESOURCE1_UNI) + self.context_slave.enter(PLACE_UNI, USER1_RESOURCE2_UNI) + assert self.protocol.method_calls == [('enter', (PLACE_UNI,))] + assert self.mrouting_table[PLACE_UNI] == set([extern_circuit, + self.resource1.circuit, + self.resource2.circuit]) + assert self.resource1.circuit.method_calls == [ + ('send', ({'_context': PLACE_UNI}, rendered(SET_PACKET)))] + self.protocol.reset_mock() + + self.context_slave.leave(PLACE_UNI, USER1_RESOURCE1_UNI) + assert self.protocol.method_calls == [] + self.context_slave.leave(PLACE_UNI, USER1_RESOURCE2_UNI) + assert self.protocol.method_calls == [('leave', (PLACE_UNI,))] + assert self.mrouting_table[PLACE_UNI] == set([extern_circuit]) + + def test_enter_leave_extern(self): + self.context_slave.enter(EXTERN_USER_UNI, USER1_RESOURCE1_UNI) + assert self.protocol.method_calls == [('enter', (EXTERN_USER_UNI,))] + assert self.mrouting_table[EXTERN_USER_UNI] == \ + set([self.resource1.circuit]) + assert self.resource1.circuit.method_calls == [ + ('send', ({'_context':EXTERN_USER_UNI}, rendered(SET_PACKET)))] + self.protocol.reset_mock() + + self.context_slave.leave(EXTERN_USER_UNI, USER1_RESOURCE1_UNI) + assert self.protocol.method_calls == [('leave', (EXTERN_USER_UNI,))] + assert EXTERN_USER_UNI not in self.mrouting_table + + def test_leave_all(self): + self.context_slave.enter(PLACE_UNI, USER1_RESOURCE1_UNI) + self.protocol.reset_mock() + + self.context_slave.leave_all() + assert self.protocol.method_calls == [('leave', (PLACE_UNI,))] + assert self.mrouting_table[PLACE_UNI] == set() + + def test_not_leave(self): + self.context_slave.leave(PLACE_UNI) + assert self.protocol.method_calls == [] diff --git a/mjacob2/tests/test_server/test_person.py b/mjacob2/tests/test_server/test_person.py new file mode 100644 index 0000000..fc8b802 --- /dev/null +++ b/mjacob2/tests/test_server/test_person.py @@ -0,0 +1,268 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +import hmac +from hashlib import sha256 + +from mock import Mock +from nose.tools import assert_raises +from tests.constants import (SERVER1, USER1, USER1_UNI, USER2_UNI, RESOURCE, + RESOURCE1_UNI, RESOURCE2_UNI, PLACE_UNI, VARIABLES, PASSWORD, MESSAGE, + ONLINE, ERROR) +from tests.helpers import mockified + +from pypsyc.protocol import (AuthenticationError, FriendshipPendingError, + FriendshipEstablishedError, EntryDeniedError) +from pypsyc.server import Entity +from pypsyc.server.db import Database +from pypsyc.server.person import Person + + +class TestPerson(object): + def setup(self): + self.server = Mock() + self.server.hostname = SERVER1 + self.server.database = Database(':memory:') + + root = Entity(server=self.server) + self.entity = Entity(root, USER1) + + @mockified('pypsyc.server.person', ['RoutingErrorRelaying']) + def test_unknown_target_error(self, RoutingErrorRelaying): + RESOURCE1 = USER1_UNI.chain('*resource1') + RESOURCE2 = USER1_UNI.chain('*resource2') + + Entity(self.entity, '*resource1') + Entity(self.entity, '*resource2') + routing_error_relaying = RoutingErrorRelaying.return_value + person = Person(self.entity) + assert RoutingErrorRelaying.call_args_list == [((person,),)] + + person.unknown_target_error(USER2_UNI) + assert routing_error_relaying.method_calls == [ + ('relay_unknown_target_error', (RESOURCE1, USER2_UNI)), + ('relay_unknown_target_error', (RESOURCE2, USER2_UNI))] + + @mockified('pypsyc.server.person', ['RoutingErrorRelaying']) + def test_unknown_target_error(self, RoutingErrorRelaying): + RESOURCE1 = USER1_UNI.chain('*resource1') + RESOURCE2 = USER1_UNI.chain('*resource2') + + Entity(self.entity, '*resource1') + Entity(self.entity, '*resource2') + routing_error_relaying = RoutingErrorRelaying.return_value + person = Person(self.entity) + assert RoutingErrorRelaying.call_args_list == [((person,),)] + + person.delivery_failed_error(USER2_UNI, ERROR) + assert routing_error_relaying.method_calls == [ + ('relay_delivery_failed_error', (RESOURCE1, USER2_UNI, ERROR)), + ('relay_delivery_failed_error', (RESOURCE2, USER2_UNI, ERROR))] + + @mockified('pypsyc.server.person', ['LinkingServer', 'FriendshipProtocol', + 'ContextSlave']) + def test_authenticate(self, LinkingServer, FriendshipProtocol, + ContextSlave): + NONCE = 'random_nonce' + DIGEST = hmac.new(PASSWORD, NONCE, sha256).digest() + + circuit = Mock() + circuit.allowed_sources = [] + friendship_protocol = FriendshipProtocol.return_value + context_slave = ContextSlave.return_value + + person = Person(self.entity) + person.register(PASSWORD) + assert LinkingServer.call_args_list == [((person,),)] + + assert_raises(AuthenticationError, + person.authenticate, ('hmac', 'wrong', NONCE), None, RESOURCE) + + sql = "INSERT INTO friendships VALUES(?, ?, 'established')" + self.server.database.execute(sql, USER1_UNI, USER2_UNI) + + person.authenticate(('hmac', DIGEST, NONCE), circuit, RESOURCE) + assert self.entity.children[RESOURCE].circuit == circuit + assert circuit.allowed_sources == [RESOURCE1_UNI] + friendships = {USER2_UNI: {'state': 'established'}} + assert friendship_protocol.method_calls == [ + ('cast_presence', (7,)), + ('send_friendships', (RESOURCE1_UNI, friendships))] + assert context_slave.method_calls == [ + ('enter', (USER2_UNI, RESOURCE1_UNI))] + friendship_protocol.reset_mock() + context_slave.reset_mock() + + person.unlink(RESOURCE) + assert self.entity.children == {} + assert friendship_protocol.method_calls == [('cast_presence', (1,))] + assert context_slave.method_calls == [('leave_all',)] + + @mockified('pypsyc.server.person', ['MessageRelaying']) + def test_message_relay(self, MessageRelaying): + message_relaying = MessageRelaying.return_value + person = Person(self.entity) + assert MessageRelaying.call_args_list == [((person,),)] + + person.private_message_relay(RESOURCE1_UNI, USER2_UNI, MESSAGE) + person.private_message_relay(RESOURCE2_UNI, USER2_UNI, MESSAGE) + assert message_relaying.method_calls == [ + ('send_private_message', (USER2_UNI, MESSAGE))] + + @mockified('pypsyc.server.person', ['MessageRelaying']) + def test_message(self, MessageRelaying): + RESOURCE1 = USER1_UNI.chain('*resource1') + RESOURCE2 = USER1_UNI.chain('*resource2') + + Entity(self.entity, '*resource1') + Entity(self.entity, '*resource2') + message_relaying = MessageRelaying.return_value + person = Person(self.entity) + + person.private_message(USER2_UNI, MESSAGE) + assert message_relaying.method_calls == [ + ('relay_private_message', (USER2_UNI, RESOURCE1, MESSAGE)), + ('relay_private_message', (USER2_UNI, RESOURCE2, MESSAGE))] + + @mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave']) + def test_outgoing_friendship(self, FriendshipProtocol, ContextSlave): + friendship_protocol = FriendshipProtocol.return_value + context_slave = ContextSlave.return_value + person = Person(self.entity) + person.register('') + Entity(self.entity, RESOURCE) + + person.client_add_friend(USER2_UNI) + fs = {'state': 'pending'} + assert friendship_protocol.method_calls == [ + ('establish', (USER2_UNI,)), + ('send_updated_friendship', (RESOURCE1_UNI, USER2_UNI, fs))] + friendship_protocol.reset_mock() + assert_raises(FriendshipPendingError, person.client_add_friend, + USER2_UNI) + + state = person.friendship_request(USER2_UNI) + assert state == 'established' + state = person.friendship_request(USER2_UNI) + assert state == 'established' + fs = {'state': 'established'} + assert friendship_protocol.method_calls == [ + ('send_updated_friendship', (RESOURCE1_UNI, USER2_UNI, fs))] + assert context_slave.method_calls == [('enter', (USER2_UNI,))] + assert_raises(FriendshipEstablishedError, person.client_add_friend, + USER2_UNI) + + @mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave']) + def test_incoming_friendship(self, FriendshipProtocol, ContextSlave): + friendship_protocol = FriendshipProtocol.return_value + context_slave = ContextSlave.return_value + person = Person(self.entity) + person.register('') + Entity(self.entity, RESOURCE) + + state = person.friendship_request(USER2_UNI) + assert state == 'pending' + state = person.friendship_request(USER2_UNI) + assert state == 'pending' + fs = {'state': 'offered'} + assert friendship_protocol.method_calls == [ + ('send_updated_friendship', (RESOURCE1_UNI, USER2_UNI, fs))] + friendship_protocol.reset_mock() + + person.client_add_friend(USER2_UNI) + fs = {'state': 'established'} + assert friendship_protocol.method_calls == [ + ('establish', (USER2_UNI,)), + ('send_updated_friendship', (RESOURCE1_UNI, USER2_UNI, fs))] + assert context_slave.method_calls == [('enter', (USER2_UNI,))] + assert_raises(FriendshipEstablishedError, person.client_add_friend, + USER2_UNI) + + @mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave']) + def test_cancel_friendship(self, FriendshipProtocol, ContextSlave): + friendship_protocol = FriendshipProtocol.return_value + context_slave = ContextSlave.return_value + person = Person(self.entity) + person.register('') + Entity(self.entity, RESOURCE) + + person.friendship_cancel(USER2_UNI) + assert context_slave.method_calls == [('leave', (USER2_UNI,))] + assert friendship_protocol.method_calls == [ + ('send_removed_friendship', (RESOURCE1_UNI, USER2_UNI))] + + @mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave']) + def test_remove_friend(self, FriendshipProtocol, ContextSlave): + friendship_protocol = FriendshipProtocol.return_value + context_slave = ContextSlave.return_value + person = Person(self.entity) + person.register('') + Entity(self.entity, RESOURCE) + + person.client_remove_friend(USER2_UNI) + assert context_slave.method_calls == [('leave', (USER2_UNI,))] + assert friendship_protocol.method_calls == [ + ('remove', (USER2_UNI,)), + ('send_removed_friendship', (RESOURCE1_UNI, USER2_UNI))] + + @mockified('pypsyc.server.person', ['FriendshipProtocol', 'ContextSlave', + 'ContextMasterProtocol']) + def test_enter_context(self, FriendshipProtocol, ContextSlave, + ContextMasterProtocol): + cm = self.entity.context_master = Mock() + cm.add_member.return_value = VARIABLES + person = Person(self.entity) + person.register('') + person.friendship_request(USER2_UNI) + person.client_add_friend(USER2_UNI) + assert ContextMasterProtocol.call_args_list == [((person,),)] + + state = person.enter_request(USER2_UNI) + assert state == VARIABLES + assert cm.method_calls == [('add_member', (USER2_UNI,))] + + def test_enter_context_entry_denied(self): + person = Person(self.entity) + person.register('') + + assert_raises(EntryDeniedError, person.enter_request, USER2_UNI) + + def test_leave_context(self): + cm = self.entity.context_master = Mock() + person = Person(self.entity) + + person.leave_context(USER2_UNI) + assert cm.method_calls == [('remove_member', (USER2_UNI,))] + + @mockified('pypsyc.server.person', ['FriendshipProtocol']) + def test_client_presence(self, FriendshipProtocol): + friendship_protocol = FriendshipProtocol.return_value + + person = Person(self.entity) + assert FriendshipProtocol.call_args_list == [((person,),)] + + person.client_presence(ONLINE) + assert friendship_protocol.method_calls == [ + ('cast_presence', (ONLINE,))] + + @mockified('pypsyc.server.person', ['ContextSlave', + 'ClientInterfaceProtocol']) + def test_client_subscribe(self, ContextSlave, ClientInterfaceProtocol): + context_slave = ContextSlave.return_value + person = Person(self.entity) + assert ContextSlave.call_args_list == [((person,),)] + assert ClientInterfaceProtocol.call_args_list == [((person,),)] + + person.client_subscribe(PLACE_UNI, RESOURCE1_UNI) + assert context_slave.method_calls == [ + ('enter', (PLACE_UNI, RESOURCE1_UNI))] + + @mockified('pypsyc.server.person', ['ContextSlave']) + def test_client_unsubscribe(self, ContextSlave): + context_slave = ContextSlave.return_value + person = Person(self.entity) + + person.client_unsubscribe(PLACE_UNI, RESOURCE1_UNI) + assert context_slave.method_calls == [ + ('leave', (PLACE_UNI, RESOURCE1_UNI))] diff --git a/mjacob2/tests/test_server/test_place.py b/mjacob2/tests/test_server/test_place.py new file mode 100644 index 0000000..5acfd4a --- /dev/null +++ b/mjacob2/tests/test_server/test_place.py @@ -0,0 +1,68 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +from mock import Mock +from tests.constants import (SERVER1, USER1_UNI, USER2_UNI, USER1_NICK, + RESOURCE1_UNI, RESOURCE2_UNI, PLACE, VARIABLES, MESSAGE) +from tests.helpers import mockified + +from pypsyc.server import Entity +from pypsyc.server.place import Place + + +class TestPlace(object): + def setup(self): + self.server = Mock() + self.server.hostname = SERVER1 + + root = Entity(server=self.server) + self.entity = Entity(root, PLACE) + self.entity.context_master = Mock() + + @mockified('pypsyc.server.place', ['ContextProtocol', + 'ConferencingProtocol']) + def test_subscription(self, ContextProtocol, ConferencingProtocol): + conferencing_protocol = ConferencingProtocol.return_value + self.entity.context_master.add_member.return_value = VARIABLES + + place = Place(self.entity) + assert ContextProtocol.call_args_list == [((place,),)] + + state = place.enter_request(USER1_UNI) + assert state == VARIABLES + assert conferencing_protocol.method_calls == [ + ('cast_member_entered', (USER1_UNI, USER1_NICK))] + assert self.entity.context_master.method_calls == [ + ('add_member', (USER1_UNI,))] + + @mockified('pypsyc.server.place', ['ConferencingProtocol']) + def test_leave(self, ConferencingProtocol): + conferencing_protocol = ConferencingProtocol.return_value + place = Place(self.entity) + + place.enter_request(USER1_UNI) + self.entity.context_master.reset_mock() + conferencing_protocol.reset_mock() + + place.leave_context(USER1_UNI) + assert self.entity.context_master.method_calls == [ + ('remove_member', (USER1_UNI,))] + assert conferencing_protocol.method_calls == [ + ('cast_member_left', (USER1_UNI,))] + + @mockified('pypsyc.server.place', ['ConferencingProtocol']) + def test_public_message(self, ConferencingProtocol): + conferencing_protocol = ConferencingProtocol.return_value + + place = Place(self.entity) + assert ConferencingProtocol.call_args_list == [((place,),)] + place.enter_request(USER1_UNI) + place.enter_request(USER2_UNI) + place.leave_context(USER2_UNI) + conferencing_protocol.reset_mock() + + place.public_message(RESOURCE2_UNI, MESSAGE) + place.public_message(RESOURCE1_UNI, MESSAGE) + assert conferencing_protocol.method_calls == [ + ('cast_public_message', (USER1_UNI, MESSAGE))] diff --git a/mjacob2/tests/test_server/test_root.py b/mjacob2/tests/test_server/test_root.py new file mode 100644 index 0000000..0840af4 --- /dev/null +++ b/mjacob2/tests/test_server/test_root.py @@ -0,0 +1,21 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +from mock import Mock +from tests.constants import SERVER1 + +from pypsyc.server import Entity +from pypsyc.server.root import Root + + +class TestRoot(object): + def setup(self): + server = Mock() + server.hostname = SERVER1 + + root_entity = Entity(server=server) + self.root = Root(root_entity) + + def test_root(self): + pass diff --git a/mjacob2/tests/test_server/test_routing.py b/mjacob2/tests/test_server/test_routing.py new file mode 100644 index 0000000..6efc57e --- /dev/null +++ b/mjacob2/tests/test_server/test_routing.py @@ -0,0 +1,353 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +from mock import Mock +from nose.tools import assert_raises +from tests.constants import (SERVER1, SERVER2, SERVER1_UNI, SERVER2_UNI, + SERVER3_UNI, USER1, USER2, USER1_UNI, USER2_UNI, RESOURCE, RESOURCE1_UNI, + RESOURCE2_UNI, PLACE_UNI, INTERFACE, IP, PORT, CONTENT) +from tests.helpers import (inited_header, connect_circuits, mockified, + AsyncMethod, PlaceHolder) + +from pypsyc.core.mmp import Uni +from pypsyc.protocol import Error +from pypsyc.server.routing import (InvalidTargetError, InvalidSourceError, + ServerCircuit, _TreeNode, Routing) +from pypsyc.util import DNSError + + +class TestServerCircuit(object): + def setup(self): + self.sc = ServerCircuit() + self.sc.factory = self.routing = Mock() + self.sc.psyc = Mock() + self.sc.allowed_sources.append(SERVER2_UNI) + + def _send(self, header): + self.sc.packet_received(inited_header(header), CONTENT) + + def test_withtarget(self): + HEADER1 = {'_target': SERVER1_UNI} + HEADER2 = {'_target': SERVER1_UNI, '_source': SERVER2_UNI} + HEADER3 = {'_target': SERVER1_UNI, '_source': SERVER3_UNI} + + self._send(HEADER1) + assert self.routing.method_calls == [ + ('route_singlecast', (HEADER1, CONTENT))] + + self._send(HEADER2) + assert self.routing.method_calls == [ + ('route_singlecast', (HEADER1, CONTENT)), + ('route_singlecast', (HEADER2, CONTENT))] + + self._send(HEADER3) + assert self.routing.method_calls == [ + ('route_singlecast', (HEADER1, CONTENT)), + ('route_singlecast', (HEADER2, CONTENT))] + + def test_withouttarget(self): + HEADER1 = {} + HEADER2 = {'_source': SERVER2_UNI} + HEADER3 = {'_source': SERVER3_UNI} + + self._send(HEADER1) + assert self.routing.method_calls == [] + assert self.sc.psyc.method_calls == [ + ('handle_packet', (HEADER1, CONTENT))] + + self._send(HEADER2) + assert self.routing.method_calls == [] + assert self.sc.psyc.method_calls == [ + ('handle_packet', (HEADER1, CONTENT)), + ('handle_packet', (HEADER2, CONTENT))] + + self._send(HEADER3) + assert self.routing.method_calls == [] + assert self.sc.psyc.method_calls == [ + ('handle_packet', (HEADER1, CONTENT)), + ('handle_packet', (HEADER2, CONTENT))] + + def test_context(self): + HEADER1 = {'_context': PLACE_UNI} + HEADER2 = {'_context': PLACE_UNI, '_target': USER1_UNI} + + self._send(HEADER1) + assert self.routing.method_calls == [ + ('route_multicast', (HEADER1, CONTENT))] + + self._send(HEADER2) + assert self.routing.method_calls == [ + ('route_multicast', (HEADER1, CONTENT)), + ('route_singlecast', (HEADER2, CONTENT))] + + def test_context_withsource(self): + HEADER1 = {'_context': PLACE_UNI, '_source': USER1_UNI} + HEADER2 = {'_context': PLACE_UNI, '_source': USER2_UNI, + '_target': USER1_UNI} + + assert_raises(NotImplementedError, self._send, HEADER1) + assert self.routing.method_calls == [] + + assert_raises(NotImplementedError, self._send, HEADER2) + assert self.routing.method_calls == [] + + def test_verification(self): + sc1, sc2 = connect_circuits(ServerCircuit(), ServerCircuit()) + sc2.factory = Mock() + + sc1.request_verification(SERVER1_UNI, SERVER2_UNI) + assert sc2.factory.method_calls == [ + ('verify_address', (sc2, SERVER1_UNI, SERVER2_UNI))] + assert type(sc2.factory.method_calls[0][1][1]) is Uni + assert sc1.allowed_sources == [SERVER2_UNI] + assert sc2.allowed_sources == [SERVER1_UNI] + assert type(sc2.allowed_sources[0]) is Uni + + def test_verification_invalid_source(self): + sc1, sc2 = connect_circuits(ServerCircuit(), ServerCircuit()) + sc2.factory = Mock() + sc2.factory.verify_address.side_effect = InvalidSourceError + + assert_raises(InvalidSourceError, sc1.request_verification, + SERVER1_UNI, SERVER2_UNI) + assert sc1.allowed_sources == [] + assert sc2.allowed_sources == [] + + def test_verification_invalid_target(self): + sc1, sc2 = connect_circuits(ServerCircuit(), ServerCircuit()) + sc2.factory = Mock() + sc2.factory.verify_address.side_effect = InvalidTargetError + + assert_raises(InvalidTargetError, sc1.request_verification, + SERVER1_UNI, SERVER2_UNI) + assert sc1.allowed_sources == [] + assert sc2.allowed_sources == [] + + def test_connection_lost(self): + root = self.routing.root = _TreeNode() + person = Mock() + person_entity = _TreeNode(root, USER1) + person_entity.packages = {'person': person} + self.sc.allowed_sources.append(RESOURCE1_UNI) + + self.routing.srouting_table = {SERVER2: Mock()} + self.sc.connectionLost(None) + assert self.routing.srouting_table == {} + assert person.method_calls == [('unlink', (RESOURCE,))] + + +def test_treenode(): + root = _TreeNode() + assert root._root == root + + n1 = _TreeNode(root, 'n1') + assert root.children == {'n1': n1} + assert n1._parent == root + assert n1._root == root + + n2 = _TreeNode(n1, 'n2') + assert n1.children == {'n2': n2} + assert n2._parent == n1 + assert n2._root == root + + +class StubEntity(_TreeNode): + def __init__(self, *args, **kwds): + _TreeNode.__init__(self, *args, **kwds) + self.headers = [] + + def handle_packet(self, header, contents): + self.headers.append(header) + +EXTERN_HEADER = {'_source': USER1_UNI, '_target': SERVER2_UNI} + +class TestRouting(object): + def test_sroute_local(self): + HEADER1 = {'_target': SERVER1_UNI} + HEADER2 = {'_target': USER1_UNI} + HEADER3 = {'_target': RESOURCE2_UNI} + + root = StubEntity() + user1 = StubEntity(root, USER1) + user2 = StubEntity(root, USER2) + home = StubEntity(user2, RESOURCE) + routing = Routing(SERVER1, INTERFACE) + routing.init(root) + + routing.route_singlecast(inited_header(HEADER1), CONTENT) + routing.route_singlecast(inited_header(HEADER2), CONTENT) + routing.route_singlecast(inited_header(HEADER3), CONTENT) + assert root.headers == [HEADER1] + assert user1.headers == [HEADER2] + assert user2.headers == [] + assert home.headers == [HEADER3] + + def test_sroute_unkown_target(self): + HEADER1 = inited_header({'_source': USER1_UNI, '_target': USER2_UNI}) + HEADER2 = inited_header({'_target': USER2_UNI, '_tag': 'tag'}) + KWDS = {'mc': '_error_unknown_target', '_uni': USER2_UNI, 'data': None} + + root = StubEntity() + root.sendmsg = Mock() + routing = Routing(SERVER1, INTERFACE) + routing.init(root) + + routing.route_singlecast(HEADER1, CONTENT) + routing.route_singlecast(HEADER2, CONTENT) + assert root.sendmsg.call_args_list == [ + ((USER1_UNI, None, None), KWDS), + ((None, None, {'_tag_relay': 'tag'}), KWDS)] + assert root.headers == [] + + def test_sroute_extern(self): + routing = Routing(SERVER1, INTERFACE) + circuit = routing.srouting_table[SERVER2] = Mock() + + routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT) + assert circuit.method_calls == [(('send'), (EXTERN_HEADER, CONTENT))] + + @mockified('pypsyc.server.routing', ['resolve_hostname', 'connect']) + def test_sroute_extern_queued(self, resolve_hostname, connect): + resolve_hostname.return_value = IP, PORT + connected = connect.side_effect = AsyncMethod() + root = Mock() + root.uni = SERVER1_UNI + routing = Routing(SERVER1, INTERFACE) + routing.init(root) + + routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT) + routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT) + assert connect.call_args_list == [ + ((IP, PORT, routing), {'bindAddress': (INTERFACE, 0)})] + + circuit = Mock() + connected.callback(circuit) + assert circuit.method_calls == [ + ('request_verification', (SERVER1_UNI, SERVER2_UNI)), + ('send', (EXTERN_HEADER, CONTENT)), + ('send', (EXTERN_HEADER, CONTENT))] + assert type(circuit.method_calls[0][1][1]) is Uni + assert routing.srouting_table == {SERVER2: circuit} + assert routing.queues == {} + + @mockified('pypsyc.server.routing', ['resolve_hostname']) + def test_sroute_extern_resolution_fail(self, resolve_hostname): + KWDS = {'mc': '_failure_unsuccessful_delivery', '_uni': SERVER2_UNI} + resolved = resolve_hostname.side_effect = AsyncMethod() + data_ph = KWDS['data'] = PlaceHolder() + root = Mock() + routing = Routing(SERVER1, INTERFACE) + routing.init(root) + + routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT) + routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT) + resolved.errback(DNSError) + assert root.method_calls == [ + ('sendmsg', (USER1_UNI, None, None), KWDS)] * 2 + assert 'resolve' in data_ph.obj + assert routing.queues == {} + + @mockified('pypsyc.server.routing', ['resolve_hostname', 'connect']) + def test_sroute_extern_connection_fail(self, resolve_hostname, connect): + KWDS = {'mc': '_failure_unsuccessful_delivery', '_uni': SERVER2_UNI} + resolve_hostname.return_value = None, None + connected = connect.side_effect = AsyncMethod() + data_ph = KWDS['data'] = PlaceHolder() + root = Mock() + routing = Routing(SERVER1, INTERFACE) + routing.init(root) + + routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT) + connected.errback(Exception('error')) + assert root.method_calls == [ + ('sendmsg', (USER1_UNI, None, None), KWDS)] + assert 'connect' in data_ph.obj + assert routing.queues == {} + + @mockified('pypsyc.server.routing', ['resolve_hostname', 'connect']) + def test_sroute_extern_verification_fail(self, resolve_hostname, connect): + KWDS = {'mc': '_failure_unsuccessful_delivery', '_uni': SERVER2_UNI} + resolve_hostname.return_value = None, None + circuit = connect.return_value + verified = circuit.request_verification.side_effect = AsyncMethod() + data_ph = KWDS['data'] = PlaceHolder() + root = Mock() + routing = Routing(SERVER1, INTERFACE) + routing.init(root) + + routing.route_singlecast(inited_header(EXTERN_HEADER), CONTENT) + verified.errback(Error) + assert root.method_calls == [ + ('sendmsg', (USER1_UNI, None, None), KWDS)] + assert 'verify' in data_ph.obj + assert routing.queues == {} + + @mockified('pypsyc.server.routing', ['resolve_hostname', 'connect']) + def test_sroute_extern_no_source(self, resolve_hostname, connect): + header = inited_header({'_target': SERVER2_UNI}) + header.source = Mock() + routing = Routing(SERVER1, INTERFACE) + + routing.route_singlecast(header, CONTENT) + header['_source'] = '' + routing.route_singlecast(header, CONTENT) + assert resolve_hostname.call_args_list == [] + assert connect.call_args_list == [] + assert routing.srouting_table == {} + assert routing.queues == {} + + def test_mroute(self): + HEADER = {'_context': PLACE_UNI} + circuit1 = Mock() + circuit2 = Mock() + routing = Routing(SERVER1, INTERFACE) + routing.mrouting_table[PLACE_UNI] = [circuit1, circuit2] + + routing.route_multicast(inited_header(HEADER), iter(CONTENT)) + assert circuit1.method_calls == [('send', (HEADER, CONTENT))] + assert circuit2.method_calls == [('send', (HEADER, CONTENT))] + + @mockified('pypsyc.server.routing', ['reactor']) + def test_listen(self, reactor): + routing = Routing(SERVER1, INTERFACE) + routing.listen(PORT) + assert reactor.method_calls == [ + ('listenTCP', (PORT, routing), {'interface': INTERFACE})] + + def _setup_verification(self): + root = Mock() + root.uni = SERVER1_UNI + routing = Routing(SERVER1, INTERFACE) + routing.init(root) + circuit = Mock() + circuit.transport.client = IP, 35771 + return routing, circuit + + @mockified('pypsyc.server.routing', ['resolve_hostname']) + def test_verification(self, resolve_hostname): + resolve_hostname.return_value = IP, PORT + routing, circuit = self._setup_verification() + + routing.verify_address(circuit, SERVER2_UNI, SERVER1_UNI) + assert resolve_hostname.call_args_list == [((SERVER2,),)] + assert routing.srouting_table == {SERVER2: circuit} + + @mockified('pypsyc.server.routing', ['resolve_hostname']) + def test_verification_invalid_target(self, resolve_hostname): + routing, circuit = self._setup_verification() + + assert_raises(InvalidTargetError, routing.verify_address, + circuit, SERVER2_UNI, SERVER3_UNI) + assert resolve_hostname.call_args_list == [] + assert routing.srouting_table == {} + + @mockified('pypsyc.server.routing', ['resolve_hostname']) + def test_verification_invalid_source(self, resolve_hostname): + resolve_hostname.return_value = '10.0.0.2', PORT + routing, circuit = self._setup_verification() + + assert_raises(InvalidSourceError, routing.verify_address, + circuit, SERVER2_UNI, SERVER1_UNI) + assert resolve_hostname.call_args_list == [((SERVER2,),)] + assert routing.srouting_table == {} diff --git a/mjacob2/tests/test_server/test_server.py b/mjacob2/tests/test_server/test_server.py new file mode 100644 index 0000000..3c82e41 --- /dev/null +++ b/mjacob2/tests/test_server/test_server.py @@ -0,0 +1,134 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +from contextlib import contextmanager + +from mock import Mock +from nose.tools import assert_raises +from tests.constants import (SERVER1, SERVER1_UNI, USER1, USER1_UNI, PLACE, + INTERFACE, PORT, PASSWORD) +from tests.helpers import mockified, rendered, AsyncMethod + +from pypsyc.core.psyc import PSYCPacket +from pypsyc.server import Entity, Server +from pypsyc.server.routing import _TreeNode + + +class TestEntity(object): + def setup(self): + self.server = Mock() + self.server.hostname = SERVER1 + self.entity = Entity(server=self.server) + + def test_uni(self): + assert self.entity.uni == SERVER1_UNI + assert Entity(self.entity, USER1).uni == USER1_UNI + + def test_castmsg(self): + HEADER = {'_context': self.entity.uni} + PACKET = PSYCPacket(mc='_message_public', data="Hello") + + self.entity.castmsg(PACKET) + assert self.server.routing.method_calls == [ + ('route_multicast', (HEADER, rendered(PACKET)))] + + def test_castmsg_kwds(self): + HEADER = {'_context': self.entity.uni} + PACKET = PSYCPacket(mc='_message_public', data="Hello") + + self.entity.castmsg(mc='_message_public', data="Hello") + assert self.server.routing.method_calls == [ + ('route_multicast', (HEADER, rendered(PACKET)))] + + +def StubEntity(*args, **kwds): + entity = _TreeNode(*args, **kwds) + entity.packages = {} + return entity + +class TestServer(object): + @mockified('pypsyc.server', ['Routing', 'Entity', 'run_webif', 'signal']) + def test_server1(self, Routing, Entity, run_webif, signal): + WEBIF_PORT = 8080 + routing = Routing.return_value + root = Entity.return_value = StubEntity() + run_webif.side_effect = AsyncMethod() + + server = Server(SERVER1, INTERFACE, PORT, WEBIF_PORT, ':memory:') + assert Routing.call_args_list == [((SERVER1, INTERFACE),)] + assert Entity.call_args_list == [({'server': server},)] + assert routing.method_calls == [('init', (root,)), ('listen', (PORT,))] + assert run_webif.call_args_list == [ + ((server, INTERFACE, WEBIF_PORT, None),)] + assert signal.signal.called + + with mockified('pypsyc.server', ['iter_entry_points', 'Entity']) as x: + with self._test_load_package(root, *x) as packages: + assert server.add_package(None, 'package') == packages[0] + assert server.add_place('place') == packages[1] + assert server.add_package(PLACE, 'package') == packages[2] + assert server.add_package(USER1, 'package') == packages[3] + person = server.register_person('user1', PASSWORD) + assert person == packages[4] + assert person.method_calls == [('register', (PASSWORD,))] + assert_raises(AssertionError, server.add_package, None, 'package') + + self._test_server2(server.database) + + @mockified('pypsyc.server', ['Routing', 'Entity', 'Database', 'run_webif']) + def _test_server2(self, database, Routing, Entity, Database, run_webif): + routing = Routing.return_value + root = Entity.return_value = StubEntity() + Database.return_value = database + executed = database.execute = AsyncMethod() + + server = Server(SERVER1, INTERFACE, 0, 0, ':memory:') + assert routing.method_calls == [('init', (root,))] + assert run_webif.call_args_list == [] + + with mockified('pypsyc.server', ['iter_entry_points', 'Entity']) as x: + with self._test_load_package(root, *x): + executed.callback() + assert_raises(AssertionError, server.add_package, None, 'package1') + + @contextmanager + def _test_load_package(self, root, iter_entry_points, Entity): + package_classes = [Mock(), Mock(), Mock(), Mock(), Mock()] + entrypoint = Mock() + entrypoint.load.side_effect = iter(package_classes).next + iter_entry_points.return_value = [entrypoint] + Entity.side_effect = StubEntity + + packages = [package.return_value for package in package_classes] + yield packages + assert Entity.call_args_list == [((root, PLACE),), ((root, USER1),)] + assert iter_entry_points.call_args_list == [ + (('pypsyc.server.packages', 'package'),), + (('pypsyc.server.packages', 'place'),), + (('pypsyc.server.packages', 'package'),), + (('pypsyc.server.packages', 'package'),), + (('pypsyc.server.packages', 'person'),)] + place = root.children[PLACE] + person = root.children[USER1] + assert package_classes[0].call_args_list == [((root,),)] + assert package_classes[1].call_args_list == [((place,),)] + assert package_classes[2].call_args_list == [((place,),)] + assert package_classes[3].call_args_list == [((person,),)] + assert package_classes[4].call_args_list == [((person,),)] + assert root.packages == {'package': packages[0]} + assert place.packages == {'place': packages[1], 'package': packages[2]} + assert person.packages == {'package': packages[3], + 'person': packages[4]} + + @mockified('pypsyc.server', ['Routing', 'Entity', 'Database', 'signal', + 'reactor']) + def test_shutdown(self, Routing, Entity, Database, signal, reactor): + database = Database.return_value + database.fetch.return_value = () + server = Server(SERVER1, INTERFACE, 0, 0, ':memory') + database.reset_mock() + + server.shutdown() + assert database.method_calls == [('stop',)] + assert reactor.method_calls == [('stop',)] diff --git a/mjacob2/tests/test_server/test_webif/test_webif.py b/mjacob2/tests/test_server/test_webif/test_webif.py new file mode 100644 index 0000000..3305db0 --- /dev/null +++ b/mjacob2/tests/test_server/test_webif/test_webif.py @@ -0,0 +1,139 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +from mock import Mock, sentinel +from nose.tools import assert_raises +from tests.constants import USER1, USER1_NICK, PASSWORD +from tests.helpers import mockified, PlaceHolder + +from pypsyc.server import webif +from pypsyc.server.db import Database +from pypsyc.server.webif import app, PersistentStore, run_webif + + +class TestViews(object): + @classmethod + def setup_class(cls): + app.secret_key = 'testing key' + + def setup(self): + self.server = webif.server = Mock() + self.server.root.children = {} + self.client = app.test_client() + + def test_index(self): + rv = self.client.get('/') + assert 'register' in rv.data + + def _register(self, username, password, password2): + return self.client.post('/register', data=dict( + username=username, password=password, password2=password2 + ), follow_redirects=True) + + def test_register_get(self): + rv = self.client.get('/register') + assert 'Username' in rv.data + assert 'Register' in rv.data + + def test_register_post(self): + rv = self._register(USER1_NICK, PASSWORD, PASSWORD) + assert 'you were registered' in rv.data + assert self.server.method_calls == [ + ('register_person', (USER1_NICK, PASSWORD))] + + def test_register_nousername(self): + rv = self._register('', '', '') + assert 'please specify a valid username' in rv.data + + def test_register_username_inuse(self): + self.server.root.children = {USER1: True} + + rv = self._register(USER1_NICK, '', '') + assert 'username already in use' in rv.data + + def test_register_nopassword(self): + rv = self._register(USER1_NICK, '', '') + assert 'your password must have at least 6 characters' in rv.data + + def test_register_shortpassword(self): + rv = self._register(USER1_NICK, 'passw', 'passw') + assert 'your password must have at least 6 characters' in rv.data + + def test_register_unmatching_passwords(self): + rv = self._register(USER1_NICK, PASSWORD, 'password2') + assert 'the two passwords do not match' in rv.data + + +def test_persistent_store(): + webif.server = Mock() + webif.server.database = Database(':memory:') + + store = PersistentStore() + assert_raises(KeyError, store.__getitem__, 'key') + assert dict(store) == {} + assert len(store) == 0 + + store['key'] = 'value' + assert store['key'] == 'value' + assert len(store) == 1 + + store['key'] = 'value2' + assert dict(store) == {'key': 'value2'} + assert len(store) == 1 + + del store['key'] + assert dict(store) == {} + assert len(store) == 0 + + +@mockified('pypsyc.server.webif', ['PersistentStore', 'urandom', + 'WSGIResource', 'Site', 'reactor', + 'greenlet']) +def test_run_webif_ssl(PersistentStore, urandom, WSGIResource, Site, reactor, + greenlet): + store = PersistentStore.return_value = {'secret_key': 'key1'*5} + reactor_ph = PlaceHolder() + threadpool_ph = PlaceHolder() + wsgi_resource = WSGIResource.return_value + site = Site.return_value + + run_webif(sentinel.server, sentinel.interface, sentinel.port, + sentinel.context_factory) + assert webif.server == sentinel.server + assert PersistentStore.call_args_list == [()] + assert webif.store == store + assert app.secret_key == 'key1'*5 + assert not urandom.called + assert WSGIResource.call_args_list == [ + ((reactor_ph, threadpool_ph, app.wsgi_app),)] + assert Site.call_args_list == [((wsgi_resource,),)] + assert reactor.method_calls == [ + ('listenSSL', (sentinel.port, site, sentinel.context_factory), + {'interface': sentinel.interface})] + + func = Mock() + reactor_ph.obj.callFromThread(func, sentinel.a, b=sentinel.b) + assert func.call_args_list == [((sentinel.a,), {'b': sentinel.b})] + + threadpool_ph.obj.callInThread(sentinel.func, sentinel.a, b=sentinel.b) + assert greenlet.call_args_list == [((sentinel.func,),)] + assert greenlet.return_value.method_calls == [ + ('switch', (sentinel.a,), {'b': sentinel.b})] + + +@mockified('pypsyc.server.webif', ['PersistentStore', 'urandom', + 'WSGIResource', 'Site', 'reactor']) +def test_run_webif_tcp(PersistentStore, urandom, WSGIResource, Site, reactor): + store = PersistentStore.return_value = {} + rand = urandom.return_value = 'key2'*5 + wsgi_resource = WSGIResource.return_value + site = Site.return_value + + run_webif(sentinel.server, sentinel.interface, sentinel.port, None) + assert urandom.call_args_list == [((20,),)] + assert app.secret_key == rand + assert store['secret_key'] == rand + assert Site.call_args_list == [((wsgi_resource,),)] + assert reactor.method_calls == [ + ('listenTCP', (sentinel.port, site), {'interface': 'localhost'})] diff --git a/mjacob2/tests/test_util.py b/mjacob2/tests/test_util.py new file mode 100644 index 0000000..0aeaf7d --- /dev/null +++ b/mjacob2/tests/test_util.py @@ -0,0 +1,206 @@ +""" + :copyright: 2010 by Manuel Jacob + :license: MIT +""" +from greenlet import greenlet +from mock import Mock, sentinel +from nose.tools import assert_raises +from tests.constants import SERVER1, IP, PORT +from tests.helpers import mockified, check_success, StubException +from twisted.internet.defer import Deferred, fail +from twisted.names.dns import RRHeader, Record_A, Record_SRV +from twisted.names.error import DNSNameError +from twisted.python.failure import Failure + +from pypsyc.util import (scheduler, schedule, Waiter, DNSError, + resolve_hostname, _PSYCConnector, connect, Event) + + +@mockified('pypsyc.util', ['greenlet']) +def test_schedule_scheduler(greenlet): + func = Mock() + + schedule(func) + assert greenlet.call_args_list == [((func, scheduler),)] + assert greenlet.return_value.method_calls == [('switch',)] + +orig_greenlet = greenlet +@mockified('pypsyc.util', ['greenlet', 'reactor']) +def test_schedule_nonscheduler(greenlet, reactor): + func = Mock() + + orig_greenlet(schedule).switch(func) + assert greenlet.call_args_list == [((func, scheduler),)] + assert reactor.method_calls == [ + ('callLater', (0, greenlet.return_value.switch))] + + +class TestWaiter(object): + def test_callback_sync(self): + waiter = Waiter() + + waiter.callback(sentinel.value) + ret = waiter.get() + assert ret == sentinel.value + + def test_errback_sync(self): + e = StubException() + waiter = Waiter() + + waiter.errback(e) + assert_raises(StubException, waiter.get) + + @check_success + def test_callback_async(self): + waiter = Waiter() + + def _test(): + ret = waiter.get() + assert ret == sentinel.value + self.success = True + greenlet(_test).switch() + waiter.callback(sentinel.value) + + @check_success + def test_errback_async(self): + e = StubException() + waiter = Waiter() + + def _test(): + assert_raises(StubException, waiter.get) + self.success = True + greenlet(_test).switch() + waiter.errback(e) + + +class TestResolveHostname(object): + @check_success + @mockified('pypsyc.util', ['lookupService', 'getHostByName']) + def test_resolve_hostname_srv(self, lookupService, getHostByName): + SERVICE = '_psyc._tcp.%s.' % SERVER1 + srv_rr = RRHeader(name=SERVICE, type=Record_SRV.TYPE, + payload=Record_SRV(target=SERVER1, port=PORT)) + a_rr = RRHeader(name=SERVER1, type=Record_A.TYPE, + payload=Record_A(IP)) + looked_up = lookupService.return_value = Deferred() + + def _test(): + ip, port = resolve_hostname(SERVER1) + assert ip == IP + assert port == PORT + assert lookupService.call_args_list == [((SERVICE,),)] + self.success = True + schedule(_test) + looked_up.callback(([srv_rr], [], [a_rr])) + + @check_success + @mockified('pypsyc.util', ['lookupService', 'getHostByName']) + def test_resolve_hostname_a(self, lookupService, getHostByName): + lookupService.side_effect = DNSNameError + looked_up = getHostByName.return_value = Deferred() + + def _test(): + ip, port = resolve_hostname(SERVER1) + assert ip == IP + assert port == PORT + assert getHostByName.call_args_list == [((SERVER1,),)] + self.success = True + schedule(_test) + looked_up.callback(IP) + + @check_success + @mockified('pypsyc.util', ['lookupService', 'getHostByName']) + def test_resolve_hostname_a_error(self, lookupService, getHostByName): + lookupService.side_effect = DNSNameError + getHostByName.return_value = fail(DNSNameError()) + + def _test(): + assert_raises(DNSError, resolve_hostname, SERVER1) + self.success = True + schedule(_test) + + +class TestConnector(object): + @mockified('pypsyc.util', ['reactor']) + def setup(self, reactor): + self.reactor = reactor + self.factory = Mock() + self.circuit = self.factory.buildProtocol.return_value + self.connector = _PSYCConnector(SERVER1, PORT, self.factory, 30, None) + self.transport = Mock() + self.connector._makeTransport = lambda: self.transport + self.connector.cancelTimeout = Mock() + + @check_success + def test_connect(self): + def _test(): + circuit = self.connector.connect() + assert circuit == self.circuit + self.success = True + schedule(_test) + assert self.connector.state == 'connecting' + assert self.connector.transport == self.transport + assert self.connector.timeoutID == self.reactor.callLater.return_value + + self.connector.buildProtocol(None) + assert self.connector.state == 'connected' + assert self.connector.cancelTimeout.called + assert self.factory.method_calls == [('buildProtocol', (None,))] + + self.circuit.inited() + + @check_success + def test_connection_failed(self): + def _test(): + assert_raises(StubException, self.connector.connect) + self.success = True + schedule(_test) + self.connector.connectionFailed(Failure(StubException())) + assert self.connector.cancelTimeout.called + assert self.connector.transport is None + assert self.connector.state == 'disconnected' + + def test_connection_lost(self): + ERROR = StubException() + self.connector.buildProtocol(None) + self.connector.connectionLost(Failure(ERROR)) + assert self.connector.state == 'disconnected' + assert self.factory.method_calls == [ + ('buildProtocol', (None,)), + ('connection_lost', (self.circuit, ERROR))] + +@mockified('pypsyc.util', ['_PSYCConnector']) +def test_connect1(_PSYCConnector): + connect(sentinel.host, sentinel.port, sentinel.factory) + assert _PSYCConnector.call_args_list == [ + ((sentinel.host, sentinel.port, sentinel.factory, 30, None),)] + assert _PSYCConnector.return_value.method_calls == [('connect',)] + +@mockified('pypsyc.util', ['_PSYCConnector']) +def test_connect2(_PSYCConnector): + connect(sentinel.host, sentinel.port, sentinel.factory, + timeout=sentinel.timeout, bindAddress=sentinel.bindAddress) + assert _PSYCConnector.call_args_list == [ + ((sentinel.host, sentinel.port, sentinel.factory, sentinel.timeout, + sentinel.bindAddress),)] + assert _PSYCConnector.return_value.method_calls == [('connect',)] + + +def test_event(): + event = Event() + f1 = Mock() + f2 = Mock() + event += f1 + event.add_observer(f2, 1, i=2) + + x = Mock() + y = Mock() + + event(x, y=y) + assert f1.call_args_list == [((x,), {'y': y})] + assert f2.call_args_list == [((x, 1), {'y': y, 'i': 2})] + + event -= f2 + event(x) + assert f1.call_args_list == [((x,), {'y': y}), ((x,),)] + assert f2.call_args_list == [((x, 1), {'y': y, 'i': 2})]