def make_router(realm_name=u'default'): """ Make a router, and return it and a RawSocket factory. """ # create a router factory router_factory = RouterFactory() # start a realm realm = RouterRealm(None, {u'name': realm_name}) router = router_factory.start_realm(realm) extra = {} session_config = ComponentConfig(realm_name, extra) realm.session = RouterServiceSession(session_config, router) # allow everything default_permissions = { u'uri': u'', u'match': u'prefix', u'allow': { u'call': True, u'register': True, u'publish': True, u'subscribe': True } } router = router_factory.get(realm_name) router.add_role(RouterRoleStaticAuth(router, 'anonymous', default_permissions=default_permissions)) # create a router session factory session_factory = RouterSessionFactory(router_factory) session_factory.add(realm.session, authrole=u'trusted') # Create a new RawSocket factory server_factory = WampRawSocketServerFactory(session_factory, {}) return router, server_factory
class TestBrokerPublish(unittest.TestCase): """ Tests for crossbar.router.broker.Broker """ def setUp(self): """ Setup router and router session factories. """ # create a router factory self.router_factory = RouterFactory('node1', 'router1', None) # start a realm self.realm = RouterRealm(None, None, {'name': 'realm1'}) self.router_factory.start_realm(self.realm) # allow everything self.router = self.router_factory.get('realm1') self.router.add_role( RouterRoleStaticAuth(self.router, 'test_role', default_permissions={ 'uri': 'com.example.', 'match': 'prefix', 'allow': { 'call': True, 'register': True, 'publish': True, 'subscribe': True, } })) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) def tearDown(self): pass def test_add(self): """ Create an application session and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): txaio.resolve(d, None) session = TestSession(types.ComponentConfig('realm1')) self.session_factory.add(session, self.router) return d def test_application_session_internal_error(self): """ simulate an internal error triggering the 'onJoin' error-case from RouterApplicationSession's send() method (from the Hello msg) """ # setup the_exception = RuntimeError("sadness") errors = [] class TestSession(ApplicationSession): def onJoin(self, *args, **kw): raise the_exception def onUserError(self, fail, msg): errors.append((fail, msg)) session = TestSession(types.ComponentConfig('realm1')) from crossbar.router.session import RouterApplicationSession # Note to self: original code was logging directly in # RouterApplicationSession -- which *may* actually be better? # or not... with mock.patch.object(RouterApplicationSession, 'log') as logger: # this should call onJoin, triggering our error self.session_factory.add(session, self.router) if True: self.assertEqual(1, len(errors), "Didn't see our error") self.assertEqual(the_exception, errors[0][0].value) else: # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertEqual(call[1][0].value, the_exception) def test_router_session_internal_error_onHello(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onHello """ # setup transport = mock.MagicMock() transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory( ) # __call__ on the _RouterSessionFactory session.onHello = boom session.onOpen(transport) msg = message.Hello('realm1', dict(caller=role.RoleCallerFeatures())) with mock.patch.object(session, 'log') as logger: # do the test; should call onHello which is now "boom", above session.onMessage(msg) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'warn') self.assertTrue('err' in call[2]) self.assertEqual(call[2]['err'].value, the_exception) def test_router_session_internal_error_onAuthenticate(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onAuthenticate) """ # setup transport = mock.MagicMock() transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory( ) # __call__ on the _RouterSessionFactory session.onAuthenticate = boom session.onOpen(transport) msg = message.Authenticate('bogus signature') # do the test; should call onHello which is now "boom", above session.onMessage(msg) errors = self.flushLoggedErrors() self.assertEqual(1, len(errors), "Expected just one error: {}".format(errors)) self.assertTrue(the_exception in [fail.value for fail in errors]) def test_router_session_goodbye_custom_message(self): """ Reason should be propagated properly from Goodbye message """ from crossbar.router.session import RouterApplicationSession session = ApplicationSession() session.onLeave = mock.Mock() session._realm = 'realm' router = Router(factory=mock.Mock(), realm=RouterRealm( controller=None, id='realm', config=dict(name='realm'), )) rap = RouterApplicationSession(session, router) rap.send(message.Goodbye('wamp.reason.logout', 'some custom message')) leaves = session.onLeave.mock_calls self.assertEqual(1, len(leaves)) details = leaves[0][1][0] self.assertEqual('wamp.reason.logout', details.reason) self.assertEqual('some custom message', details.message) def test_router_session_goodbye_onLeave_error(self): """ Reason should be propagated properly from Goodbye message """ from crossbar.router.session import RouterApplicationSession session = ApplicationSession() the_exception = RuntimeError("onLeave fails") def boom(*args, **kw): raise the_exception session.onLeave = mock.Mock(side_effect=boom) session._realm = 'realm' router = Router(factory=mock.Mock(), realm=RouterRealm( controller=None, id='realm', config=dict(name='realm'), )) rap = RouterApplicationSession(session, router) rap.send(message.Goodbye('wamp.reason.logout', 'some custom message')) errors = self.flushLoggedErrors() self.assertEqual(1, len(errors)) self.assertEqual(the_exception, errors[0].value) def test_router_session_goodbye_fire_disconnect_error(self): """ Reason should be propagated properly from Goodbye message """ from crossbar.router.session import RouterApplicationSession session = ApplicationSession() the_exception = RuntimeError("sad times at ridgemont high") def boom(*args, **kw): if args[0] == 'disconnect': return defer.fail(the_exception) return defer.succeed(None) session.fire = mock.Mock(side_effect=boom) session._realm = 'realm' router = Router(factory=mock.Mock(), realm=RouterRealm( controller=None, id='realm', config=dict(name='realm'), )) rap = RouterApplicationSession(session, router) rap.send(message.Goodbye('wamp.reason.logout', 'some custom message')) errors = self.flushLoggedErrors() self.assertEqual(1, len(errors)) self.assertEqual(the_exception, errors[0].value) def test_router_session_lifecycle(self): """ We see all 'lifecycle' notifications. """ from crossbar.router.session import RouterApplicationSession def mock_fire(name, *args, **kw): fired.append(name) return defer.succeed(None) fired = [] session = ApplicationSession() session._realm = 'realm' session.fire = mock.Mock(side_effect=mock_fire) router = Router(factory=mock.Mock(), realm=RouterRealm( controller=None, id='realm', config=dict(name='realm'), )) rap = RouterApplicationSession(session, router) # we never fake out the 'Welcome' message, so there will be no # 'ready' notification... rap.send(message.Goodbye('wamp.reason.logout', 'some custom message')) self.assertTrue('connect' in fired) self.assertTrue('join' in fired) self.assertTrue('ready' in fired) self.assertTrue('leave' in fired) self.assertTrue('disconnect' in fired) def test_add_and_subscribe(self): """ Create an application session that subscribes to some topic and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): d2 = self.subscribe(lambda: None, 'com.example.topic1') def ok(_): txaio.resolve(d, None) def error(err): txaio.reject(d, err) txaio.add_callbacks(d2, ok, error) session = TestSession(types.ComponentConfig('realm1')) self.session_factory.add(session, self.router, authrole='test_role') return d def test_publish_closed_session(self): """ ensure a session doesn't get Events if it's closed (see also issue #431) """ # we want to trigger a deeply-nested condition in # processPublish in class Broker -- lets try w/o refactoring # anything first... class TestSession(ApplicationSession): pass session0 = TestSession() session1 = TestSession() router = mock.MagicMock() router.new_correlation_id = lambda: 'fake correlation id' broker = Broker(router, reactor) # let's just "cheat" our way a little to the right state by # injecting our subscription "directly" (e.g. instead of # faking out an entire Subscribe etc. flow # ...so we need _subscriptions_map to have at least one # subscription (our test one) for the topic we'll publish to broker._subscription_map.add_observer(session0, 'test.topic') # simulate the session state we want, which is that a # transport is connected (._transport != None) but there # _session_id *is* None (not joined yet, or left already) self.assertIs(None, session0._session_id) session0._transport = mock.MagicMock() session0._transport.get_channel_id = mock.MagicMock( return_value=b'deadbeef') session1._session_id = 1234 # "from" session should look connected + joined session1._transport = mock.MagicMock() session1._transport.channel_id = b'aaaabeef' # here's the main "cheat"; we're faking out the # router.authorize because we need it to callback immediately router.authorize = mock.MagicMock( return_value=txaio.create_future_success( dict(allow=True, cache=False, disclose=True))) # now we scan call "processPublish" such that we get to the # condition we're interested in (this "comes from" session1 # beacuse by default publishes don't go to the same session) pubmsg = message.Publish(123, 'test.topic') broker.processPublish(session1, pubmsg) # neither session should have sent anything on its transport self.assertEquals(session0._transport.method_calls, []) self.assertEquals(session1._transport.method_calls, []) def test_publish_traced_events(self): """ with two subscribers and message tracing the last event should have a magic flag """ # we want to trigger a deeply-nested condition in # processPublish in class Broker -- lets try w/o refactoring # anything first... class TestSession(ApplicationSession): pass session0 = TestSession() session1 = TestSession() session2 = TestSession() router = mock.MagicMock() router.send = mock.Mock() router.new_correlation_id = lambda: 'fake correlation id' router.is_traced = True broker = Broker(router, reactor) # let's just "cheat" our way a little to the right state by # injecting our subscription "directly" (e.g. instead of # faking out an entire Subscribe etc. flow # ...so we need _subscriptions_map to have at least one # subscription (our test one) for the topic we'll publish to broker._subscription_map.add_observer(session0, 'test.topic') broker._subscription_map.add_observer(session1, 'test.topic') session0._session_id = 1000 session0._transport = mock.MagicMock() session0._transport.get_channel_id = mock.MagicMock( return_value=b'deadbeef') session1._session_id = 1001 session1._transport = mock.MagicMock() session1._transport.get_channel_id = mock.MagicMock( return_value=b'deadbeef') session2._session_id = 1002 session2._transport = mock.MagicMock() session2._transport.get_channel_id = mock.MagicMock( return_value=b'deadbeef') # here's the main "cheat"; we're faking out the # router.authorize because we need it to callback immediately router.authorize = mock.MagicMock( return_value=txaio.create_future_success( dict(allow=True, cache=False, disclose=True))) # now we scan call "processPublish" such that we get to the # condition we're interested in (this "comes from" session1 # beacuse by default publishes don't go to the same session) pubmsg = message.Publish(123, 'test.topic') broker.processPublish(session2, pubmsg) # extract all the event calls events = [ call[1][1] for call in router.send.mock_calls if call[1][0] in [session0, session1, session2] ] self.assertEqual(2, len(events)) self.assertFalse(events[0].correlation_is_last) self.assertTrue(events[1].correlation_is_last) def test_publish_traced_events_batched(self): """ with two subscribers and message tracing the last event should have a magic flag """ # we want to trigger a deeply-nested condition in # processPublish in class Broker -- lets try w/o refactoring # anything first... class TestSession(ApplicationSession): pass session0 = TestSession() session1 = TestSession() session2 = TestSession() session3 = TestSession() session4 = TestSession() # NOTE! We ensure that "session0" (the publishing session) is # *last* in the observation-list to trigger a (now fixed) # edge-case) sessions = [session1, session2, session3, session4, session0] router = mock.MagicMock() router.send = mock.Mock() router.new_correlation_id = lambda: 'fake correlation id' router.is_traced = True clock = Clock() with replace_loop(clock): broker = Broker(router, clock) broker._options.event_dispatching_chunk_size = 2 # to ensure we get "session0" last, we turn on ordering in # the observations broker._subscription_map._ordered = 1 # let's just "cheat" our way a little to the right state by # injecting our subscription "directly" (e.g. instead of # faking out an entire Subscribe etc. flow # ...so we need _subscriptions_map to have at least one # subscription (our test one) for the topic we'll publish to for session in sessions: broker._subscription_map.add_observer(session, 'test.topic') for i, sess in enumerate(sessions): sess._session_id = 1000 + i sess._transport = mock.MagicMock() sess._transport.get_channel_id = mock.MagicMock( return_value=b'deadbeef') # here's the main "cheat"; we're faking out the # router.authorize because we need it to callback immediately router.authorize = mock.MagicMock( return_value=txaio.create_future_success( dict(allow=True, cache=False, disclose=True))) # now we scan call "processPublish" such that we get to the # condition we're interested in; should go to all sessions # except session0 pubmsg = message.Publish(123, 'test.topic') broker.processPublish(session0, pubmsg) clock.advance(1) clock.advance(1) # extract all the event calls events = [ call[1][1] for call in router.send.mock_calls if call[1][0] in [session0, session1, session2, session3, session4] ] # all except session0 should have gotten an event, and # session4's should have the "last" flag set self.assertEqual(4, len(events)) self.assertFalse(events[0].correlation_is_last) self.assertFalse(events[1].correlation_is_last) self.assertFalse(events[2].correlation_is_last) self.assertTrue(events[3].correlation_is_last) def test_subscribe_unsubscribe(self): """ Make sure the wamp.* event sequence delivered as part of a subscribe/unsubscribe sequence is correct and contains the correct session id and subscription id values. """ router = self.router test = self class Session(mock.MagicMock): _private = [] _events = [] _authrole = 'trusted' _session_id = 0 def send(self, *args, **argv): self._private.append(args[0]) def publish(self, *args, **argv): self._events.append([args, argv]) class TestSession(ApplicationSession): def __init__(self, *args, **kw): super().__init__(*args, **kw) self._service_session = Session() self._router = router def onJoin(self, details): self._service_session._session_id = details.session router.attach(self._service_session) router._broker._router._realm.session = self._service_session subscription = message.Subscribe( self._service_session._session_id, 'com.example.test1') router._broker.processSubscribe(self._service_session, subscription) subscription = message.Subscribe( self._service_session._session_id, 'com.example.test2') router._broker.processSubscribe(self._service_session, subscription) subscription = message.Subscribe( self._service_session._session_id, 'com.example.test3') router._broker.processSubscribe(self._service_session, subscription) subscriptions = [] for obj in list(self._service_session._private): subscription = message.Unsubscribe( self._service_session._session_id, subscription=obj.subscription) router._broker.processUnsubscribe(self._service_session, subscription) subscriptions.append(obj.subscription) def all_done(): created = list(subscriptions) subscribes = list(subscriptions) unsubscribes = list(subscriptions) deletes = list(subscriptions) for args, argv in self._service_session._events: if args[0] == 'wamp.subscription.on_create': test.assertEqual( args[1], self._service_session._session_id, 'on_create: session id is incorrect!') test.assertTrue( args[2]['id'] in created, 'on_create: subscription id is incorrect!') created.remove(args[2]['id']) if args[0] == 'wamp.subscription.on_subscribe': test.assertEqual( args[1], self._service_session._session_id, 'on_subscribe: session id is incorrect!') test.assertTrue( args[2] in subscribes, 'on_subscribe: subscription id is incorrect!') subscribes.remove(args[2]) if args[0] == 'wamp.subscription.on_unsubscribe': test.assertEqual( args[1], self._service_session._session_id, 'on_unsubscribe: session id is incorrect!') test.assertTrue( args[2] in unsubscribes, 'on_unsubscribe: subscription id is incorrect!' ) unsubscribes.remove(args[2]) if args[0] == 'wamp.subscription.on_delete': test.assertEqual( args[1], self._service_session._session_id, 'on_delete: session id is incorrect!') test.assertTrue( args[2] in deletes, 'on_delete: subscription id is incorrect!') deletes.remove(args[2]) test.assertEqual( len(created), 0, 'incorrect response sequence for on_create') test.assertEqual( len(subscribes), 0, 'incorrect response sequence for on_subscribe') test.assertEqual( len(unsubscribes), 0, 'incorrect response sequence for on_unsubscribe') test.assertEqual( len(deletes), 0, 'incorrect response sequence for on_delete') reactor.callLater(0, all_done) session = TestSession(types.ComponentConfig('realm1')) self.session_factory.add(session, self.router, authrole='trusted') def test_subscribe_detach(self): """ Make sure the wamp.* event sequence delivered as part of a subscribe/detach sequence is correct and contains the correct session id and subscription id values. """ router = self.router test = self class Session(mock.MagicMock): """ Mock the session object, this is key to capturing all the replies and publications. We get all replies in _private and publications in events, so we can issue the request we need to test, then check at the end _events contains the list of pub's we are expecting. """ _private = [] _events = [] _authrole = 'trusted' _session_id = 0 def send(self, *args, **argv): self._private.append(args[0]) def publish(self, *args, **argv): self._events.append([args, argv]) class TestSession(ApplicationSession): def __init__(self, *args, **kw): super().__init__(*args, **kw) self._service_session = Session() self._router = router def onJoin(self, details): self._service_session._session_id = details.session router.attach(self._service_session) router._broker._router._realm.session = self._service_session subscription = message.Subscribe( self._service_session._session_id, 'com.example.test1') router._broker.processSubscribe(self._service_session, subscription) subscription = message.Subscribe( self._service_session._session_id, 'com.example.test2') router._broker.processSubscribe(self._service_session, subscription) subscription = message.Subscribe( self._service_session._session_id, 'com.example.test3') router._broker.processSubscribe(self._service_session, subscription) subscriptions = [] for obj in list(self._service_session._private): subscriptions.append(obj.subscription) router.detach(self._service_session) def all_done(): # # These lists are initialised with the subscription id's we've generated # with out subscribe sequence, the following routines should decrement each # list to exactly zero. # created = list(subscriptions) subscribes = list(subscriptions) unsubscribes = list(subscriptions) deletes = list(subscriptions) for args, argv in self._service_session._events: if args[0] == 'wamp.subscription.on_create': test.assertEqual( args[1], self._service_session._session_id, 'on_create: session id is incorrect!') test.assertTrue( args[2]['id'] in created, 'on_create: subscription id is incorrect!') created.remove(args[2]['id']) if args[0] == 'wamp.subscription.on_subscribe': test.assertEqual( args[1], self._service_session._session_id, 'on_subscribe: session id is incorrect!') test.assertTrue( args[2] in subscribes, 'on_subscribe: subscription id is incorrect!') subscribes.remove(args[2]) if args[0] == 'wamp.subscription.on_unsubscribe': test.assertEqual( args[1], self._service_session._session_id, 'on_unsubscribe: session id is incorrect!') test.assertTrue( args[2] in unsubscribes, 'on_unsubscribe: subscription id is incorrect!' ) unsubscribes.remove(args[2]) if args[0] == 'wamp.subscription.on_delete': test.assertEqual( args[1], self._service_session._session_id, 'on_delete: session id is incorrect!') test.assertTrue( args[2] in deletes, 'on_delete: subscription id is incorrect!') deletes.remove(args[2]) test.assertEqual( len(created), 0, 'incorrect response sequence for on_create') test.assertEqual( len(subscribes), 0, 'incorrect response sequence for on_subscribe') test.assertEqual( len(unsubscribes), 0, 'incorrect response sequence for on_unsubscribe') test.assertEqual( len(deletes), 0, 'incorrect response sequence for on_delete') reactor.callLater(0, all_done) session = TestSession(types.ComponentConfig('realm1')) self.session_factory.add(session, self.router, authrole='trusted')
class TestEmbeddedSessions(unittest.TestCase): """ Test cases for application session running embedded in router. """ def setUp(self): """ Setup router and router session factories. """ # create a router factory self.router_factory = RouterFactory(u'mynode') # start a realm self.router_factory.start_realm(RouterRealm(None, {u'name': u'realm1'})) # allow everything default_permissions = { u'uri': u'', u'match': u'prefix', u'call': True, u'register': True, u'publish': True, u'subscribe': True } router = self.router_factory.get(u'realm1') router.add_role(RouterRoleStaticAuth(router, None, default_permissions=default_permissions)) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) def tearDown(self): pass def test_add(self): """ Create an application session and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): txaio.resolve(d, None) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session) return d def test_application_session_internal_error(self): """ simulate an internal error triggering the 'onJoin' error-case from _RouterApplicationSession's send() method (from the Hello msg) """ # setup the_exception = RuntimeError("sadness") errors = [] class TestSession(ApplicationSession): def onJoin(self, *args, **kw): raise the_exception def onUserError(self, *args, **kw): errors.append((args, kw)) session = TestSession(types.ComponentConfig(u'realm1')) # in this test, we are just looking for onUserError to get # called so we don't need to patch the logger. this should # call onJoin, triggering our error self.session_factory.add(session) # check we got the right log.failure() call self.assertTrue(len(errors) > 0, "expected onUserError call") fail = errors[0][0][0] self.assertTrue(fail.value == the_exception) def test_router_session_internal_error_onHello(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onHello """ # setup transport = mock.MagicMock() transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory() # __call__ on the _RouterSessionFactory session.onHello = boom session.onOpen(transport) msg = message.Hello(u'realm1', dict(caller=role.RoleCallerFeatures())) # XXX think: why isn't this using _RouterSession.log? from crossbar.router.session import RouterSession with mock.patch.object(RouterSession, 'log') as logger: # do the test; should call onHello which is now "boom", above session.onMessage(msg) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertTrue('failure' in call[2]) self.assertEqual(call[2]['failure'].value, the_exception) def test_router_session_internal_error_onAuthenticate(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onAuthenticate) """ # setup transport = mock.MagicMock() transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory() # __call__ on the _RouterSessionFactory session.onAuthenticate = boom session.onOpen(transport) msg = message.Authenticate(u'bogus signature') # XXX think: why isn't this using _RouterSession.log? from crossbar.router.session import RouterSession with mock.patch.object(RouterSession, 'log') as logger: # do the test; should call onHello which is now "boom", above session.onMessage(msg) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertTrue('failure' in call[2]) self.assertEqual(call[2]['failure'].value, the_exception) def test_add_and_subscribe(self): """ Create an application session that subscribes to some topic and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): # noinspection PyUnusedLocal def on_event(*arg, **kwargs): pass d2 = self.subscribe(on_event, u'com.example.topic1') def ok(_): txaio.resolve(d, None) def error(err): txaio.reject(d, err) txaio.add_callbacks(d2, ok, error) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session) return d
class TestBrokerPublish(unittest.TestCase): """ Tests for crossbar.router.broker.Broker """ def setUp(self): """ Setup router and router session factories. """ # create a router factory self.router_factory = RouterFactory(None) # start a realm self.realm = RouterRealm(None, {u'name': u'realm1'}) self.router_factory.start_realm(self.realm) # allow everything self.router = self.router_factory.get(u'realm1') self.router.add_role( RouterRoleStaticAuth(self.router, u'test_role', default_permissions={ u'uri': u'com.example.', u'match': u'prefix', u'allow': { u'call': True, u'register': True, u'publish': True, u'subscribe': True, } })) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) def tearDown(self): pass def test_add(self): """ Create an application session and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): txaio.resolve(d, None) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session) return d def test_application_session_internal_error(self): """ simulate an internal error triggering the 'onJoin' error-case from RouterApplicationSession's send() method (from the Hello msg) """ # setup the_exception = RuntimeError("sadness") errors = [] class TestSession(ApplicationSession): def onJoin(self, *args, **kw): raise the_exception def onUserError(self, fail, msg): errors.append((fail, msg)) session = TestSession(types.ComponentConfig(u'realm1')) from crossbar.router.session import RouterApplicationSession # Note to self: original code was logging directly in # RouterApplicationSession -- which *may* actually be better? # or not... with mock.patch.object(RouterApplicationSession, 'log') as logger: # this should call onJoin, triggering our error self.session_factory.add(session) if True: self.assertEqual(1, len(errors), "Didn't see our error") self.assertEqual(the_exception, errors[0][0].value) else: # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertEqual(call[1][0].value, the_exception) def test_router_session_internal_error_onHello(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onHello """ # setup transport = mock.MagicMock() transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory( ) # __call__ on the _RouterSessionFactory session.onHello = boom session.onOpen(transport) msg = message.Hello(u'realm1', dict(caller=role.RoleCallerFeatures())) # XXX think: why isn't this using _RouterSession.log? from crossbar.router.session import RouterSession with mock.patch.object(RouterSession, 'log') as logger: # do the test; should call onHello which is now "boom", above session.onMessage(msg) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertTrue('failure' in call[2]) self.assertEqual(call[2]['failure'].value, the_exception) def test_router_session_internal_error_onAuthenticate(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onAuthenticate) """ # setup transport = mock.MagicMock() transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory( ) # __call__ on the _RouterSessionFactory session.onAuthenticate = boom session.onOpen(transport) msg = message.Authenticate(u'bogus signature') # do the test; should call onHello which is now "boom", above session.onMessage(msg) errors = self.flushLoggedErrors() self.assertEqual(1, len(errors), "Expected just one error: {}".format(errors)) self.assertTrue(the_exception in [fail.value for fail in errors]) def test_router_session_goodbye_custom_message(self): """ Reason should be propagated properly from Goodbye message """ from crossbar.router.session import RouterApplicationSession session = mock.Mock() session._realm = u'realm' router_factory = mock.Mock() rap = RouterApplicationSession(session, router_factory) rap.send( message.Hello(u'realm', {u'caller': role.RoleCallerFeatures()})) session.reset_mock() rap.send(message.Goodbye(u'wamp.reason.logout', u'some custom message')) leaves = [call for call in session.mock_calls if call[0] == 'onLeave'] self.assertEqual(1, len(leaves)) details = leaves[0][1][0] self.assertEqual(u'wamp.reason.logout', details.reason) self.assertEqual(u'some custom message', details.message) def test_router_session_goodbye_onLeave_error(self): """ Reason should be propagated properly from Goodbye message """ from crossbar.router.session import RouterApplicationSession session = mock.Mock() the_exception = RuntimeError("onLeave fails") def boom(*args, **kw): raise the_exception session.onLeave = mock.Mock(side_effect=boom) session._realm = u'realm' router_factory = mock.Mock() rap = RouterApplicationSession(session, router_factory) rap.send( message.Hello(u'realm', {u'caller': role.RoleCallerFeatures()})) session.reset_mock() rap.send(message.Goodbye(u'wamp.reason.logout', u'some custom message')) errors = self.flushLoggedErrors() self.assertEqual(1, len(errors)) self.assertEqual(the_exception, errors[0].value) def test_router_session_goodbye_fire_disconnect_error(self): """ Reason should be propagated properly from Goodbye message """ from crossbar.router.session import RouterApplicationSession session = mock.Mock() the_exception = RuntimeError("sad times at ridgemont high") def boom(*args, **kw): if args[0] == 'disconnect': return defer.fail(the_exception) return defer.succeed(None) session.fire = mock.Mock(side_effect=boom) session._realm = u'realm' router_factory = mock.Mock() rap = RouterApplicationSession(session, router_factory) rap.send( message.Hello(u'realm', {u'caller': role.RoleCallerFeatures()})) session.reset_mock() rap.send(message.Goodbye(u'wamp.reason.logout', u'some custom message')) errors = self.flushLoggedErrors() self.assertEqual(1, len(errors)) self.assertEqual(the_exception, errors[0].value) def test_router_session_lifecycle(self): """ We see all 'lifecycle' notifications. """ from crossbar.router.session import RouterApplicationSession def mock_fire(name, *args, **kw): fired.append(name) return defer.succeed(None) fired = [] session = mock.Mock() session._realm = u'realm' session.fire = mock.Mock(side_effect=mock_fire) router_factory = mock.Mock() rap = RouterApplicationSession(session, router_factory) # we never fake out the 'Welcome' message, so there will be no # 'ready' notification... rap.send( message.Hello(u'realm', {u'caller': role.RoleCallerFeatures()})) rap.send(message.Goodbye(u'wamp.reason.logout', u'some custom message')) self.assertTrue('connect' in fired) self.assertTrue('join' in fired) self.assertTrue('ready' in fired) self.assertTrue('leave' in fired) self.assertTrue('disconnect' in fired) def test_add_and_subscribe(self): """ Create an application session that subscribes to some topic and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): d2 = self.subscribe(lambda: None, u'com.example.topic1') def ok(_): txaio.resolve(d, None) def error(err): txaio.reject(d, err) txaio.add_callbacks(d2, ok, error) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session, authrole=u'test_role') return d def test_publish_closed_session(self): """ ensure a session doesn't get Events if it's closed (see also issue #431) """ # we want to trigger a deeply-nested condition in # processPublish in class Broker -- lets try w/o refactoring # anything first... class TestSession(ApplicationSession): pass session0 = TestSession() session1 = TestSession() router = mock.MagicMock() broker = Broker(router, reactor) # let's just "cheat" our way a little to the right state by # injecting our subscription "directly" (e.g. instead of # faking out an entire Subscribe etc. flow # ...so we need _subscriptions_map to have at least one # subscription (our test one) for the topic we'll publish to broker._subscription_map.add_observer(session0, u'test.topic') # simulate the session state we want, which is that a # transport is connected (._transport != None) but there # _session_id *is* None (not joined yet, or left already) self.assertIs(None, session0._session_id) session0._transport = mock.MagicMock() session0._transport.get_channel_id = mock.MagicMock( return_value=b'deadbeef') session1._session_id = 1234 # "from" session should look connected + joined session1._transport = mock.MagicMock() session1._transport.channel_id = b'aaaabeef' # here's the main "cheat"; we're faking out the # router.authorize because we need it to callback immediately router.authorize = mock.MagicMock( return_value=txaio.create_future_success( dict(allow=True, cache=False, disclose=True))) # now we scan call "processPublish" such that we get to the # condition we're interested in (this "comes from" session1 # beacuse by default publishes don't go to the same session) pubmsg = message.Publish(123, u'test.topic') broker.processPublish(session1, pubmsg) # neither session should have sent anything on its transport self.assertEquals(session0._transport.method_calls, []) self.assertEquals(session1._transport.method_calls, [])
class TestDealer(unittest.TestCase): """ """ def setUp(self): """ Setup router and router session factories. """ # create a router factory self.router_factory = RouterFactory(None, None) # start a realm self.realm = RouterRealm(u'realm-001', {u'name': u'realm1'}) self.router_factory.start_realm(self.realm) # allow everything self.router = self.router_factory.get(u'realm1') self.router.add_role( RouterRoleStaticAuth(self.router, u'test_role', default_permissions={ u'uri': u'com.example.', u'match': u'prefix', u'allow': { u'call': True, u'register': True, u'publish': True, u'subscribe': True, } })) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) def tearDown(self): pass @defer.inlineCallbacks def test_outstanding_invoke(self): """ When a call is pending and the callee goes away, it cancels the in-flight call """ session = mock.Mock() session._realm = u'realm1' self.router.authorize = mock.Mock( return_value=defer.succeed({ u'allow': True, u'disclose': True })) rap = RouterApplicationSession(session, self.router_factory) rap.send( message.Hello(u"realm1", {u'caller': role.RoleCallerFeatures()})) rap.send(message.Register(1, u'foo')) # we can retrieve the Registration via # session.mock_calls[-1][1][0] if req'd # re-set the authorize, as the Deferred from above is already # used-up and it gets called again to authorize the Call self.router.authorize = mock.Mock( return_value=defer.succeed({ u'allow': True, u'disclose': True })) rap.send(message.Call(42, u'foo')) orig = rap.send d = defer.Deferred() rap.send(message.Goodbye()) def wrapper(*args, **kw): d.callback(args[0]) return orig(*args, **kw) rap.send = wrapper # we can do this *after* the call to send() the Goodbye # (above) because it takes a reactor-turn to actually # process the cancel/errors etc -- hence the Deferred and # yield in this test... msg = yield d self.assertEqual(42, msg.request) self.assertEqual(u'wamp.error.canceled', msg.error) def test_outstanding_invoke_but_caller_gone(self): session = mock.Mock() outstanding = mock.Mock() outstanding.call.request = 1 dealer = self.router._dealer dealer.attach(session) dealer._callee_to_invocations[session] = [outstanding] # pretend we've disconnected already outstanding.caller._transport = None dealer.detach(session) self.assertEqual([], outstanding.mock_calls) def test_call_cancel(self): last_message = {'1': []} def session_send(msg): last_message['1'] = msg session = mock.Mock() session._transport.send = session_send session._session_roles = { 'callee': role.RoleCalleeFeatures(call_canceling=True) } dealer = self.router._dealer dealer.attach(session) def authorize(*args, **kwargs): return defer.succeed({u'allow': True, u'disclose': False}) self.router.authorize = mock.Mock(side_effect=authorize) dealer.processRegister( session, message.Register(1, u'com.example.my.proc', u'exact', message.Register.INVOKE_SINGLE, 1)) registered_msg = last_message['1'] self.assertIsInstance(registered_msg, message.Registered) dealer.processCall(session, message.Call(2, u'com.example.my.proc', [])) invocation_msg = last_message['1'] self.assertIsInstance(invocation_msg, message.Invocation) dealer.processCancel(session, message.Cancel(2)) # should receive an INTERRUPT from the dealer now interrupt_msg = last_message['1'] self.assertIsInstance(interrupt_msg, message.Interrupt) self.assertEqual(interrupt_msg.request, invocation_msg.request) dealer.processInvocationError( session, message.Error(message.Invocation.MESSAGE_TYPE, invocation_msg.request, u'wamp.error.canceled')) call_error_msg = last_message['1'] self.assertIsInstance(call_error_msg, message.Error) self.assertEqual(message.Call.MESSAGE_TYPE, call_error_msg.request_type) self.assertEqual(u'wamp.error.canceled', call_error_msg.error) def test_call_cancel_two_sessions(self): """ this has 2 different session using the same ID (42) for their Call requests to confirm we deal with the fact that these IDs are only unique per-session properly """ messages = [] def session_send(msg): messages.append(msg) session0 = mock.Mock() session0._transport.send = session_send session0._session_roles = { 'callee': role.RoleCalleeFeatures(call_canceling=True) } session1 = mock.Mock() session1._transport.send = session_send session1._session_roles = { 'callee': role.RoleCalleeFeatures(call_canceling=True) } dealer = self.router._dealer dealer.attach(session0) dealer.attach(session1) def authorize(*args, **kwargs): return defer.succeed({u'allow': True, u'disclose': False}) self.router.authorize = mock.Mock(side_effect=authorize) dealer.processRegister( session0, message.Register(1, u'com.example.my.proc', u'exact', message.Register.INVOKE_SINGLE, 2)) registered_msg = messages[-1] self.assertIsInstance(registered_msg, message.Registered) # two calls outstanding to the endpoint, both happen to use # the same ID (42) which is legal dealer.processCall(session0, message.Call(42, u'com.example.my.proc', [])) invocation_msg0 = messages[-1] self.assertIsInstance(invocation_msg0, message.Invocation) dealer.processCall(session1, message.Call(42, u'com.example.my.proc', [])) invocation_msg1 = messages[-1] self.assertIsInstance(invocation_msg1, message.Invocation) # now, cancel the first session's call dealer.processCancel(session0, message.Cancel(42)) # should receive an INTERRUPT from the dealer now (for the # correct session only) interrupt_msg0 = messages[-1] self.assertIsInstance(interrupt_msg0, message.Interrupt) self.assertEqual(interrupt_msg0.request, invocation_msg0.request) dealer.processInvocationError( session0, message.Error(message.Invocation.MESSAGE_TYPE, invocation_msg0.request, u'wamp.error.canceled')) def test_call_cancel_without_callee_support(self): last_message = {'1': []} def session_send(msg): last_message['1'] = msg session = mock.Mock() session._transport.send = session_send session._session_roles = {'callee': role.RoleCalleeFeatures()} dealer = self.router._dealer dealer.attach(session) def authorize(*args, **kwargs): return defer.succeed({u'allow': True, u'disclose': False}) self.router.authorize = mock.Mock(side_effect=authorize) dealer.processRegister( session, message.Register(1, u'com.example.my.proc', u'exact', message.Register.INVOKE_SINGLE, 1)) registered_msg = last_message['1'] self.assertIsInstance(registered_msg, message.Registered) dealer.processCall(session, message.Call(2, u'com.example.my.proc', [])) invocation_msg = last_message['1'] self.assertIsInstance(invocation_msg, message.Invocation) dealer.processCancel(session, message.Cancel(2)) # set message to None to make sure that we get nothing back last_message['1'] = None # should NOT receive an INTERRUPT from the dealer now interrupt_msg = last_message['1'] self.assertIsNone(interrupt_msg) def test_force_reregister_kick(self): """ Kick an existing registration with force_reregister=True """ session = mock.Mock() session._realm = u'realm1' self.router.authorize = mock.Mock( return_value=defer.succeed({ u'allow': True, u'disclose': True })) rap = RouterApplicationSession(session, self.router_factory) rap.send( message.Hello(u"realm1", {u'caller': role.RoleCallerFeatures()})) rap.send(message.Register(1, u'foo')) reg_id = session.mock_calls[-1][1][0].registration # re-set the authorize, as the Deferred from above is already # used-up and it gets called again to authorize the Call self.router.authorize = mock.Mock( return_value=defer.succeed({ u'allow': True, u'disclose': True })) # re-register the same procedure rap.send(message.Register(2, u'foo', force_reregister=True)) # the first procedure with 'reg_id' as the Registration ID # should have gotten kicked out unregs = [ call[1][0] for call in session.mock_calls if call[0] == 'onMessage' and isinstance(call[1][0], message.Unregistered) ] self.assertEqual(1, len(unregs)) unreg = unregs[0] self.assertEqual(0, unreg.request) self.assertEqual(reg_id, unreg.registration) def test_yield_on_unowned_invocation(self): sessionMessages = {'1': None} def session1send(msg): sessionMessages['1'] = msg def authorize(*args, **kwargs): return defer.succeed({u'allow': True, u'disclose': False}) self.router.authorize = mock.Mock(side_effect=authorize) session1 = mock.Mock() session1._transport.send = session1send session2 = mock.Mock() dealer = self.router._dealer dealer.attach(session1) dealer.attach(session2) register = message.Register(1, u'com.example.some.call', u'exact', message.Register.INVOKE_SINGLE, 1) dealer.processRegister(session1, register) registered = sessionMessages['1'] self.assertIsInstance(registered, message.Registered) call = message.Call(2, u'com.example.some.call', [], {}) dealer.processCall(session1, call) invocation = sessionMessages['1'] self.assertIsInstance(invocation, message.Invocation) yieldMsg = message.Yield(invocation.request, [u'hello'], {}) # this yield is happening on a different session than the one that # just received the invocation def yield_from_wrong_session(): dealer.processYield(session2, yieldMsg) self.failUnlessRaises(ProtocolError, yield_from_wrong_session) def test_caller_detach_interrupt_cancel_supported(self): last_message = {'1': []} def session_send(msg): last_message['1'] = msg session = mock.Mock() session._transport.send = session_send session._session_roles = { 'callee': role.RoleCalleeFeatures(call_canceling=True) } caller_session = mock.Mock() dealer = self.router._dealer dealer.attach(session) dealer.attach(caller_session) def authorize(*args, **kwargs): return defer.succeed({u'allow': True, u'disclose': False}) self.router.authorize = mock.Mock(side_effect=authorize) dealer.processRegister( session, message.Register(1, u'com.example.my.proc', u'exact', message.Register.INVOKE_SINGLE, 1)) registered_msg = last_message['1'] self.assertIsInstance(registered_msg, message.Registered) dealer.processCall(caller_session, message.Call(2, u'com.example.my.proc', [])) invocation_msg = last_message['1'] self.assertIsInstance(invocation_msg, message.Invocation) dealer.detach(caller_session) # should receive an INTERRUPT from the dealer now interrupt_msg = last_message['1'] self.assertIsInstance(interrupt_msg, message.Interrupt) self.assertEqual(interrupt_msg.request, invocation_msg.request) def test_caller_detach_interrupt_cancel_not_supported(self): last_message = {'1': []} def session_send(msg): last_message['1'] = msg session = mock.Mock() session._transport.send = session_send session._session_roles = {'callee': role.RoleCalleeFeatures()} caller_session = mock.Mock() dealer = self.router._dealer dealer.attach(session) dealer.attach(caller_session) def authorize(*args, **kwargs): return defer.succeed({u'allow': True, u'disclose': False}) self.router.authorize = mock.Mock(side_effect=authorize) dealer.processRegister( session, message.Register(1, u'com.example.my.proc', u'exact', message.Register.INVOKE_SINGLE, 1)) registered_msg = last_message['1'] self.assertIsInstance(registered_msg, message.Registered) dealer.processCall(caller_session, message.Call(2, u'com.example.my.proc', [])) invocation_msg = last_message['1'] self.assertIsInstance(invocation_msg, message.Invocation) dealer.detach(caller_session) # reset recorded message to make sure we don't receive anything last_message['1'] = None # should NOT receive an INTERRUPT from the dealer now because we don't support cancellation self.assertIsNone(last_message['1']) def test_concurrency_with_error(self): """ register a concurrency=2 method, called with errors """ callee_messages = [] caller_messages = [] def callee_send(msg): callee_messages.append(msg) session = mock.Mock() session._transport.send = callee_send session._session_roles = {'callee': role.RoleCalleeFeatures()} def caller_send(msg): caller_messages.append(msg) caller_session = mock.Mock() caller_session._transport.send = caller_send dealer = self.router._dealer dealer.attach(session) dealer.attach(caller_session) def authorize(*args, **kwargs): return defer.succeed({u'allow': True, u'disclose': False}) self.router.authorize = mock.Mock(side_effect=authorize) # we register out procedure, with concurrency=1 dealer.processRegister( session, message.Register(request=1, procedure=u'com.example.my.proc', match=u'exact', invoke=message.Register.INVOKE_SINGLE, concurrency=1)) registered_msg = callee_messages[-1] self.assertIsInstance(registered_msg, message.Registered) # we have registered our procedure that has concurrency=1 # and now we call it dealer.processCall(caller_session, message.Call(2, u'com.example.my.proc', [])) # we pretend that the call caused an error of some sort invocation_msg = callee_messages[-1] self.assertIsInstance(invocation_msg, message.Invocation) dealer.processInvocationError( session, message.Error( message.Call.MESSAGE_TYPE, invocation_msg.request, u"wamp.error.foo", )) self.assertEqual(1, len(caller_messages)) self.assertEqual( u"wamp.error.foo", caller_messages[-1].error, ) # now we call it again, which should work because the # previously-outstanding call was resolved with an error # (before bug 1105 being fixed this wouldn't work properly) dealer.processCall(caller_session, message.Call(3, u'com.example.my.proc', ['foo'])) invocation_msg = callee_messages[-1] self.assertIsInstance(invocation_msg, message.Invocation) self.assertEqual(1, len(caller_messages), "got an extra unexpected message") dealer.processYield( session, message.Yield( invocation_msg.request, args=['a result'], )) result_msg = caller_messages[-1] self.assertIsInstance(result_msg, message.Result) self.assertEqual(result_msg.args, ['a result'])
class TestDealer(unittest.TestCase): """ """ def setUp(self): """ Setup router and router session factories. """ # create a router factory self.router_factory = RouterFactory() # start a realm self.realm = RouterRealm(u'realm-001', {u'name': u'realm1'}) self.router_factory.start_realm(self.realm) # allow everything self.router = self.router_factory.get(u'realm1') self.router.add_role( RouterRoleStaticAuth(self.router, u'test_role', default_permissions={ u'uri': u'com.example.', u'match': u'prefix', u'allow': { u'call': True, u'register': True, u'publish': True, u'subscribe': True, } })) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) def tearDown(self): pass @defer.inlineCallbacks def test_outstanding_invoke(self): """ When a call is pending and the callee goes away, it cancels the in-flight call """ session = mock.Mock() session._realm = u'realm1' self.router.authorize = mock.Mock( return_value=defer.succeed({ u'allow': True, u'disclose': True })) rap = RouterApplicationSession(session, self.router_factory) rap.send( message.Hello(u"realm1", {u'caller': role.RoleCallerFeatures()})) rap.send(message.Register(1, u'foo')) # we can retrieve the Registration via # session.mock_calls[-1][1][0] if req'd # re-set the authorize, as the Deferred from above is already # used-up and it gets called again to authorize the Call self.router.authorize = mock.Mock( return_value=defer.succeed({ u'allow': True, u'disclose': True })) rap.send(message.Call(42, u'foo')) orig = rap.send d = defer.Deferred() rap.send(message.Goodbye()) def wrapper(*args, **kw): d.callback(args[0]) return orig(*args, **kw) rap.send = wrapper # we can do this *after* the call to send() the Goodbye # (above) because it takes a reactor-turn to actually # process the cancel/errors etc -- hence the Deferred and # yield in this test... msg = yield d self.assertEqual(42, msg.request) self.assertEqual(u'wamp.error.canceled', msg.error)
class TestBrokerPublish(unittest.TestCase): """ Tests for crossbar.router.broker.Broker """ def setUp(self): """ Setup router and router session factories. """ # create a router factory self.router_factory = RouterFactory(u'mynode') # start a realm self.realm = RouterRealm(None, {u'name': u'realm1'}) self.router_factory.start_realm(self.realm) # allow everything permissions = RouterPermissions('', True, True, True, True, True) self.router = self.router_factory.get(u'realm1') self.router.add_role( RouterRoleStaticAuth(self.router, None, default_permissions=permissions)) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) def tearDown(self): pass def test_add(self): """ Create an application session and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): txaio.resolve(d, None) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session) return d def test_application_session_internal_error(self): """ simulate an internal error triggering the 'onJoin' error-case from _RouterApplicationSession's send() method (from the Hello msg) """ # setup the_exception = RuntimeError("sadness") errors = [] class TestSession(ApplicationSession): def onJoin(self, *args, **kw): raise the_exception def onUserError(self, fail, msg): errors.append((fail, msg)) session = TestSession(types.ComponentConfig(u'realm1')) from crossbar.router.session import _RouterApplicationSession # Note to self: original code was logging directly in # _RouterApplicationSession -- which *may* actually be better? # or not... with mock.patch.object(_RouterApplicationSession, 'log') as logger: # this should call onJoin, triggering our error self.session_factory.add(session) if True: self.assertEqual(1, len(errors), "Didn't see our error") self.assertEqual(the_exception, errors[0][0].value) else: # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertEqual(call[1][0].value, the_exception) def test_router_session_internal_error_onHello(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onHello """ # setup transport = mock.MagicMock() the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory( ) # __call__ on the _RouterSessionFactory session.onHello = boom session.onOpen(transport) msg = message.Hello(u'realm1', dict(caller=role.RoleCallerFeatures())) # XXX think: why isn't this using _RouterSession.log? from crossbar.router.session import RouterSession with mock.patch.object(RouterSession, 'log') as logger: # do the test; should call onHello which is now "boom", above session.onMessage(msg) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertTrue('log_failure' in call[2]) self.assertEqual(call[2]['log_failure'].value, the_exception) def test_router_session_internal_error_onAuthenticate(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onAuthenticate) """ # setup transport = mock.MagicMock() the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory( ) # __call__ on the _RouterSessionFactory session.onAuthenticate = boom session.onOpen(transport) msg = message.Authenticate(u'bogus signature') # XXX think: why isn't this using _RouterSession.log? from crossbar.router.session import RouterSession with mock.patch.object(RouterSession, 'log') as logger: # do the test; should call onHello which is now "boom", above session.onMessage(msg) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertTrue('log_failure' in call[2]) self.assertEqual(call[2]['log_failure'].value, the_exception) def test_add_and_subscribe(self): """ Create an application session that subscribes to some topic and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): d2 = self.subscribe(lambda: None, u'com.example.topic1') def ok(_): txaio.resolve(d, None) def error(err): txaio.reject(d, err) txaio.add_callbacks(d2, ok, error) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session) return d def test_publish_closed_session(self): """ ensure a session doesn't get Events if it's closed (see also issue #431) """ # we want to trigger a deeply-nested condition in # processPublish in class Broker -- lets try w/o refactoring # anything first... class TestSession(ApplicationSession): pass session0 = TestSession() session1 = TestSession() router = mock.MagicMock() broker = Broker(router) # let's just "cheat" our way a little to the right state by # injecting our subscription "directly" (e.g. instead of # faking out an entire Subscribe etc. flow # ...so we need _subscriptions_map to have at least one # subscription (our test one) for the topic we'll publish to broker._subscription_map.add_observer(session0, u'test.topic') # simulate the session state we want, which is that a # transport is connected (._transport != None) but there # _session_id *is* None (not joined yet, or left already) self.assertIs(None, session0._session_id) session0._transport = mock.MagicMock() session1._session_id = 1234 # "from" session should look connected + joined session1._transport = mock.MagicMock() # here's the main "cheat"; we're faking out the # router.authorize because we need it to callback immediately router.authorize = mock.MagicMock( return_value=txaio.create_future_success(True)) # now we scan call "processPublish" such that we get to the # condition we're interested in (this "comes from" session1 # beacuse by default publishes don't go to the same session) pubmsg = message.Publish(123, u'test.topic') broker.processPublish(session1, pubmsg) # neither session should have sent anything on its transport self.assertEquals(session0._transport.method_calls, []) self.assertEquals(session1._transport.method_calls, [])
class TestEmbeddedSessions(unittest.TestCase): """ Test cases for application session running embedded in router. """ def setUp(self): """ Setup router and router session factories. """ # create a router factory self.router_factory = RouterFactory() # start a realm self.router_factory.start_realm(RouterRealm(None, {u"name": u"realm1"})) # allow everything permissions = RouterPermissions("", True, True, True, True, True) router = self.router_factory.get(u"realm1") router.add_role(RouterRoleStaticAuth(router, None, default_permissions=permissions)) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) def tearDown(self): pass def test_add(self): """ Create an application session and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): txaio.resolve(d, None) session = TestSession(types.ComponentConfig(u"realm1")) self.session_factory.add(session) return d def test_application_session_internal_error(self): """ simulate an internal error triggering the 'onJoin' error-case from _RouterApplicationSession's send() method (from the Hello msg) """ # setup the_exception = RuntimeError("sadness") class TestSession(ApplicationSession): def onJoin(self, *args, **kw): raise the_exception session = TestSession(types.ComponentConfig(u"realm1")) from crossbar.router.session import _RouterApplicationSession # execute, first patching-out the logger so we can see that # log.failure() was called when our exception triggers. with mock.patch.object(_RouterApplicationSession, "log") as logger: # this should call onJoin, triggering our error self.session_factory.add(session) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], "failure") self.assertTrue("failure" in call[2]) self.assertEqual(call[2]["failure"].value, the_exception) def test_router_session_internal_error_onHello(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onHello """ # setup transport = mock.MagicMock() the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory() # __call__ on the _RouterSessionFactory session.onHello = boom session.onOpen(transport) msg = message.Hello(u"realm1", dict(caller=role.RoleCallerFeatures())) # XXX think: why isn't this using _RouterSession.log? from crossbar.router.session import RouterSession with mock.patch.object(RouterSession, "log") as logger: # do the test; should call onHello which is now "boom", above session.onMessage(msg) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], "failure") self.assertTrue("failure" in call[2]) self.assertEqual(call[2]["failure"].value, the_exception) def test_router_session_internal_error_onAuthenticate(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onAuthenticate) """ # setup transport = mock.MagicMock() the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory() # __call__ on the _RouterSessionFactory session.onAuthenticate = boom session.onOpen(transport) msg = message.Authenticate(u"bogus signature") # XXX think: why isn't this using _RouterSession.log? from crossbar.router.session import RouterSession with mock.patch.object(RouterSession, "log") as logger: # do the test; should call onHello which is now "boom", above session.onMessage(msg) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], "failure") self.assertTrue("failure" in call[2]) self.assertEqual(call[2]["failure"].value, the_exception) def test_add_and_subscribe(self): """ Create an application session that subscribes to some topic and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): # noinspection PyUnusedLocal def on_event(*arg, **kwargs): pass d2 = self.subscribe(on_event, u"com.example.topic1") def ok(_): txaio.resolve(d, None) def error(err): txaio.reject(d, err) txaio.add_callbacks(d2, ok, error) session = TestSession(types.ComponentConfig(u"realm1")) self.session_factory.add(session) return d
class TestDealer(unittest.TestCase): """ """ def setUp(self): """ Setup router and router session factories. """ # create a router factory self.router_factory = RouterFactory(None, None) # start a realm self.realm = RouterRealm(u'realm-001', {u'name': u'realm1'}) self.router_factory.start_realm(self.realm) # allow everything self.router = self.router_factory.get(u'realm1') self.router.add_role( RouterRoleStaticAuth( self.router, u'test_role', default_permissions={ u'uri': u'com.example.', u'match': u'prefix', u'allow': { u'call': True, u'register': True, u'publish': True, u'subscribe': True, } } ) ) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) def tearDown(self): pass @defer.inlineCallbacks def test_outstanding_invoke(self): """ When a call is pending and the callee goes away, it cancels the in-flight call """ session = mock.Mock() session._realm = u'realm1' self.router.authorize = mock.Mock( return_value=defer.succeed({u'allow': True, u'disclose': True}) ) rap = RouterApplicationSession(session, self.router_factory) rap.send(message.Hello(u"realm1", {u'caller': role.RoleCallerFeatures()})) rap.send(message.Register(1, u'foo')) # we can retrieve the Registration via # session.mock_calls[-1][1][0] if req'd # re-set the authorize, as the Deferred from above is already # used-up and it gets called again to authorize the Call self.router.authorize = mock.Mock( return_value=defer.succeed({u'allow': True, u'disclose': True}) ) rap.send(message.Call(42, u'foo')) orig = rap.send d = defer.Deferred() rap.send(message.Goodbye()) def wrapper(*args, **kw): d.callback(args[0]) return orig(*args, **kw) rap.send = wrapper # we can do this *after* the call to send() the Goodbye # (above) because it takes a reactor-turn to actually # process the cancel/errors etc -- hence the Deferred and # yield in this test... msg = yield d self.assertEqual(42, msg.request) self.assertEqual(u'wamp.error.canceled', msg.error) def test_outstanding_invoke_but_caller_gone(self): session = mock.Mock() outstanding = mock.Mock() outstanding.call.request = 1 dealer = self.router._dealer dealer.attach(session) dealer._callee_to_invocations[session] = [outstanding] # pretend we've disconnected already outstanding.caller._transport = None dealer.detach(session) self.assertEqual([], outstanding.mock_calls) def test_call_cancel(self): last_message = {'1': []} def session_send(msg): last_message['1'] = msg session = mock.Mock() session._transport.send = session_send session._session_roles = {'callee': role.RoleCalleeFeatures(call_canceling=True)} dealer = self.router._dealer dealer.attach(session) def authorize(*args, **kwargs): return defer.succeed({u'allow': True, u'disclose': False}) self.router.authorize = mock.Mock(side_effect=authorize) dealer.processRegister(session, message.Register( 1, u'com.example.my.proc', u'exact', message.Register.INVOKE_SINGLE, 1 )) registered_msg = last_message['1'] self.assertIsInstance(registered_msg, message.Registered) dealer.processCall(session, message.Call( 2, u'com.example.my.proc', [] )) invocation_msg = last_message['1'] self.assertIsInstance(invocation_msg, message.Invocation) dealer.processCancel(session, message.Cancel( 2 )) # should receive an INTERRUPT from the dealer now interrupt_msg = last_message['1'] self.assertIsInstance(interrupt_msg, message.Interrupt) self.assertEqual(interrupt_msg.request, invocation_msg.request) dealer.processInvocationError(session, message.Error( message.Invocation.MESSAGE_TYPE, invocation_msg.request, u'wamp.error.canceled' )) call_error_msg = last_message['1'] self.assertIsInstance(call_error_msg, message.Error) self.assertEqual(message.Call.MESSAGE_TYPE, call_error_msg.request_type) self.assertEqual(u'wamp.error.canceled', call_error_msg.error) def test_call_cancel_two_sessions(self): """ this has 2 different session using the same ID (42) for their Call requests to confirm we deal with the fact that these IDs are only unique per-session properly """ messages = [] def session_send(msg): messages.append(msg) session0 = mock.Mock() session0._transport.send = session_send session0._session_roles = {'callee': role.RoleCalleeFeatures(call_canceling=True)} session1 = mock.Mock() session1._transport.send = session_send session1._session_roles = {'callee': role.RoleCalleeFeatures(call_canceling=True)} dealer = self.router._dealer dealer.attach(session0) dealer.attach(session1) def authorize(*args, **kwargs): return defer.succeed({u'allow': True, u'disclose': False}) self.router.authorize = mock.Mock(side_effect=authorize) dealer.processRegister(session0, message.Register( 1, u'com.example.my.proc', u'exact', message.Register.INVOKE_SINGLE, 2 )) registered_msg = messages[-1] self.assertIsInstance(registered_msg, message.Registered) # two calls outstanding to the endpoint, both happen to use # the same ID (42) which is legal dealer.processCall(session0, message.Call( 42, u'com.example.my.proc', [] )) invocation_msg0 = messages[-1] self.assertIsInstance(invocation_msg0, message.Invocation) dealer.processCall(session1, message.Call( 42, u'com.example.my.proc', [] )) invocation_msg1 = messages[-1] self.assertIsInstance(invocation_msg1, message.Invocation) # now, cancel the first session's call dealer.processCancel(session0, message.Cancel( 42 )) # should receive an INTERRUPT from the dealer now (for the # correct session only) interrupt_msg0 = messages[-1] self.assertIsInstance(interrupt_msg0, message.Interrupt) self.assertEqual(interrupt_msg0.request, invocation_msg0.request) dealer.processInvocationError(session0, message.Error( message.Invocation.MESSAGE_TYPE, invocation_msg0.request, u'wamp.error.canceled' )) def test_call_cancel_without_callee_support(self): last_message = {'1': []} def session_send(msg): last_message['1'] = msg session = mock.Mock() session._transport.send = session_send session._session_roles = {'callee': role.RoleCalleeFeatures()} dealer = self.router._dealer dealer.attach(session) def authorize(*args, **kwargs): return defer.succeed({u'allow': True, u'disclose': False}) self.router.authorize = mock.Mock(side_effect=authorize) dealer.processRegister(session, message.Register( 1, u'com.example.my.proc', u'exact', message.Register.INVOKE_SINGLE, 1 )) registered_msg = last_message['1'] self.assertIsInstance(registered_msg, message.Registered) dealer.processCall(session, message.Call( 2, u'com.example.my.proc', [] )) invocation_msg = last_message['1'] self.assertIsInstance(invocation_msg, message.Invocation) dealer.processCancel(session, message.Cancel( 2 )) # set message to None to make sure that we get nothing back last_message['1'] = None # should NOT receive an INTERRUPT from the dealer now interrupt_msg = last_message['1'] self.assertIsNone(interrupt_msg) def test_force_reregister_kick(self): """ Kick an existing registration with force_reregister=True """ session = mock.Mock() session._realm = u'realm1' self.router.authorize = mock.Mock( return_value=defer.succeed({u'allow': True, u'disclose': True}) ) rap = RouterApplicationSession(session, self.router_factory) rap.send(message.Hello(u"realm1", {u'caller': role.RoleCallerFeatures()})) rap.send(message.Register(1, u'foo')) reg_id = session.mock_calls[-1][1][0].registration # re-set the authorize, as the Deferred from above is already # used-up and it gets called again to authorize the Call self.router.authorize = mock.Mock( return_value=defer.succeed({u'allow': True, u'disclose': True}) ) # re-register the same procedure rap.send(message.Register(2, u'foo', force_reregister=True)) # the first procedure with 'reg_id' as the Registration ID # should have gotten kicked out unregs = [ call[1][0] for call in session.mock_calls if call[0] == 'onMessage' and isinstance(call[1][0], message.Unregistered) ] self.assertEqual(1, len(unregs)) unreg = unregs[0] self.assertEqual(0, unreg.request) self.assertEqual(reg_id, unreg.registration) def test_yield_on_unowned_invocation(self): sessionMessages = {'1': None} def session1send(msg): sessionMessages['1'] = msg def authorize(*args, **kwargs): return defer.succeed({u'allow': True, u'disclose': False}) self.router.authorize = mock.Mock(side_effect=authorize) session1 = mock.Mock() session1._transport.send = session1send session2 = mock.Mock() dealer = self.router._dealer dealer.attach(session1) dealer.attach(session2) register = message.Register(1, u'com.example.some.call', u'exact', message.Register.INVOKE_SINGLE, 1) dealer.processRegister(session1, register) registered = sessionMessages['1'] self.assertIsInstance(registered, message.Registered) call = message.Call(2, u'com.example.some.call', [], {}) dealer.processCall(session1, call) invocation = sessionMessages['1'] self.assertIsInstance(invocation, message.Invocation) yieldMsg = message.Yield(invocation.request, [u'hello'], {}) # this yield is happening on a different session than the one that # just received the invocation def yield_from_wrong_session(): dealer.processYield(session2, yieldMsg) self.failUnlessRaises(ProtocolError, yield_from_wrong_session) def test_caller_detach_interrupt_cancel_supported(self): last_message = {'1': []} def session_send(msg): last_message['1'] = msg session = mock.Mock() session._transport.send = session_send session._session_roles = {'callee': role.RoleCalleeFeatures(call_canceling=True)} caller_session = mock.Mock() dealer = self.router._dealer dealer.attach(session) dealer.attach(caller_session) def authorize(*args, **kwargs): return defer.succeed({u'allow': True, u'disclose': False}) self.router.authorize = mock.Mock(side_effect=authorize) dealer.processRegister(session, message.Register( 1, u'com.example.my.proc', u'exact', message.Register.INVOKE_SINGLE, 1 )) registered_msg = last_message['1'] self.assertIsInstance(registered_msg, message.Registered) dealer.processCall(caller_session, message.Call( 2, u'com.example.my.proc', [] )) invocation_msg = last_message['1'] self.assertIsInstance(invocation_msg, message.Invocation) dealer.detach(caller_session) # should receive an INTERRUPT from the dealer now interrupt_msg = last_message['1'] self.assertIsInstance(interrupt_msg, message.Interrupt) self.assertEqual(interrupt_msg.request, invocation_msg.request) def test_caller_detach_interrupt_cancel_not_supported(self): last_message = {'1': []} def session_send(msg): last_message['1'] = msg session = mock.Mock() session._transport.send = session_send session._session_roles = {'callee': role.RoleCalleeFeatures()} caller_session = mock.Mock() dealer = self.router._dealer dealer.attach(session) dealer.attach(caller_session) def authorize(*args, **kwargs): return defer.succeed({u'allow': True, u'disclose': False}) self.router.authorize = mock.Mock(side_effect=authorize) dealer.processRegister(session, message.Register( 1, u'com.example.my.proc', u'exact', message.Register.INVOKE_SINGLE, 1 )) registered_msg = last_message['1'] self.assertIsInstance(registered_msg, message.Registered) dealer.processCall(caller_session, message.Call( 2, u'com.example.my.proc', [] )) invocation_msg = last_message['1'] self.assertIsInstance(invocation_msg, message.Invocation) dealer.detach(caller_session) # reset recorded message to make sure we don't receive anything last_message['1'] = None # should NOT receive an INTERRUPT from the dealer now because we don't support cancellation self.assertIsNone(last_message['1']) def test_concurrency_with_error(self): """ register a concurrency=2 method, called with errors """ callee_messages = [] caller_messages = [] def callee_send(msg): callee_messages.append(msg) session = mock.Mock() session._transport.send = callee_send session._session_roles = {'callee': role.RoleCalleeFeatures()} def caller_send(msg): caller_messages.append(msg) caller_session = mock.Mock() caller_session._transport.send = caller_send dealer = self.router._dealer dealer.attach(session) dealer.attach(caller_session) def authorize(*args, **kwargs): return defer.succeed({u'allow': True, u'disclose': False}) self.router.authorize = mock.Mock(side_effect=authorize) # we register out procedure, with concurrency=1 dealer.processRegister(session, message.Register( request=1, procedure=u'com.example.my.proc', match=u'exact', invoke=message.Register.INVOKE_SINGLE, concurrency=1 )) registered_msg = callee_messages[-1] self.assertIsInstance(registered_msg, message.Registered) # we have registered our procedure that has concurrency=1 # and now we call it dealer.processCall(caller_session, message.Call( 2, u'com.example.my.proc', [] )) # we pretend that the call caused an error of some sort invocation_msg = callee_messages[-1] self.assertIsInstance(invocation_msg, message.Invocation) dealer.processInvocationError( session, message.Error( message.Call.MESSAGE_TYPE, invocation_msg.request, u"wamp.error.foo", ) ) self.assertEqual(1, len(caller_messages)) self.assertEqual( u"wamp.error.foo", caller_messages[-1].error, ) # now we call it again, which should work because the # previously-outstanding call was resolved with an error # (before bug 1105 being fixed this wouldn't work properly) dealer.processCall(caller_session, message.Call( 3, u'com.example.my.proc', ['foo'] )) invocation_msg = callee_messages[-1] self.assertIsInstance(invocation_msg, message.Invocation) self.assertEqual(1, len(caller_messages), "got an extra unexpected message") dealer.processYield( session, message.Yield( invocation_msg.request, args=['a result'], ) ) result_msg = caller_messages[-1] self.assertIsInstance(result_msg, message.Result) self.assertEqual(result_msg.args, ['a result'])
class TestEmbeddedSessions(unittest.TestCase): """ Test cases for application session running embedded in router. """ def setUp(self): """ Setup router and router session factories. """ # create a router factory self.router_factory = RouterFactory() # start a realm self.router_factory.start_realm(RouterRealm(None, {u'name': u'realm1'})) # allow everything permissions = RouterPermissions('', True, True, True, True, True) router = self.router_factory.get(u'realm1') router.add_role( RouterRoleStaticAuth(router, None, default_permissions=permissions)) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) def tearDown(self): pass def test_add(self): """ Create an application session and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): txaio.resolve(d, None) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session) return d def _test_application_session_internal_error(self): """ simulate an internal error triggering the 'onJoin' error-case from _RouterApplicationSession's send() method (from the Hello msg) """ # setup the_exception = RuntimeError("sadness") class TestSession(ApplicationSession): def onJoin(self, *args, **kw): raise the_exception session = TestSession(types.ComponentConfig(u'realm1')) from crossbar.router.session import _RouterApplicationSession # execute, first patching-out the logger so we can see that # log.failure() was called when our exception triggers. with mock.patch.object(_RouterApplicationSession, 'log') as logger: # this should call onJoin, triggering our error self.session_factory.add(session) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertTrue('log_failure' in call[2]) self.assertEqual(call[2]['log_failure'].value, the_exception) def test_router_session_internal_error_onHello(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onHello """ # setup transport = mock.MagicMock() the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory( ) # __call__ on the _RouterSessionFactory session.onHello = boom session.onOpen(transport) msg = message.Hello(u'realm1', dict(caller=role.RoleCallerFeatures())) # XXX think: why isn't this using _RouterSession.log? from crossbar.router.session import RouterSession with mock.patch.object(RouterSession, 'log') as logger: # do the test; should call onHello which is now "boom", above session.onMessage(msg) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertTrue('log_failure' in call[2]) self.assertEqual(call[2]['log_failure'].value, the_exception) def test_router_session_internal_error_onAuthenticate(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onAuthenticate) """ # setup transport = mock.MagicMock() the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory( ) # __call__ on the _RouterSessionFactory session.onAuthenticate = boom session.onOpen(transport) msg = message.Authenticate(u'bogus signature') # XXX think: why isn't this using _RouterSession.log? from crossbar.router.session import RouterSession with mock.patch.object(RouterSession, 'log') as logger: # do the test; should call onHello which is now "boom", above session.onMessage(msg) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertTrue('log_failure' in call[2]) self.assertEqual(call[2]['log_failure'].value, the_exception) def test_add_and_subscribe(self): """ Create an application session that subscribes to some topic and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): # noinspection PyUnusedLocal def on_event(*arg, **kwargs): pass d2 = self.subscribe(on_event, u'com.example.topic1') def ok(_): txaio.resolve(d, None) def error(err): txaio.reject(d, err) txaio.add_callbacks(d2, ok, error) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session) return d
class TestBrokerPublish(unittest.TestCase): """ Tests for crossbar.router.broker.Broker """ def setUp(self): """ Setup router and router session factories. """ # create a router factory self.router_factory = RouterFactory(u'mynode') # start a realm self.realm = RouterRealm(None, {u'name': u'realm1'}) self.router_factory.start_realm(self.realm) # allow everything self.router = self.router_factory.get(u'realm1') self.router.add_role( RouterRoleStaticAuth( self.router, u'test_role', default_permissions={ u'uri': u'com.example.', u'match': u'prefix', u'allow': { u'call': True, u'register': True, u'publish': True, u'subscribe': True, } } ) ) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) def tearDown(self): pass def test_add(self): """ Create an application session and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): txaio.resolve(d, None) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session) return d def test_application_session_internal_error(self): """ simulate an internal error triggering the 'onJoin' error-case from RouterApplicationSession's send() method (from the Hello msg) """ # setup the_exception = RuntimeError("sadness") errors = [] class TestSession(ApplicationSession): def onJoin(self, *args, **kw): raise the_exception def onUserError(self, fail, msg): errors.append((fail, msg)) session = TestSession(types.ComponentConfig(u'realm1')) from crossbar.router.session import RouterApplicationSession # Note to self: original code was logging directly in # RouterApplicationSession -- which *may* actually be better? # or not... with mock.patch.object(RouterApplicationSession, 'log') as logger: # this should call onJoin, triggering our error self.session_factory.add(session) if True: self.assertEqual(1, len(errors), "Didn't see our error") self.assertEqual(the_exception, errors[0][0].value) else: # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertEqual(call[1][0].value, the_exception) def test_router_session_internal_error_onHello(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onHello """ # setup transport = mock.MagicMock() transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory() # __call__ on the _RouterSessionFactory session.onHello = boom session.onOpen(transport) msg = message.Hello(u'realm1', dict(caller=role.RoleCallerFeatures())) # XXX think: why isn't this using _RouterSession.log? from crossbar.router.session import RouterSession with mock.patch.object(RouterSession, 'log') as logger: # do the test; should call onHello which is now "boom", above session.onMessage(msg) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertTrue('failure' in call[2]) self.assertEqual(call[2]['failure'].value, the_exception) def test_router_session_internal_error_onAuthenticate(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onAuthenticate) """ # setup transport = mock.MagicMock() transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory() # __call__ on the _RouterSessionFactory session.onAuthenticate = boom session.onOpen(transport) msg = message.Authenticate(u'bogus signature') # do the test; should call onHello which is now "boom", above session.onMessage(msg) errors = self.flushLoggedErrors() self.assertEqual(1, len(errors), "Expected just one error: {}".format(errors)) self.assertTrue(the_exception in [fail.value for fail in errors]) def test_add_and_subscribe(self): """ Create an application session that subscribes to some topic and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): d2 = self.subscribe(lambda: None, u'com.example.topic1') def ok(_): txaio.resolve(d, None) def error(err): txaio.reject(d, err) txaio.add_callbacks(d2, ok, error) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session, authrole=u'test_role') return d def test_publish_closed_session(self): """ ensure a session doesn't get Events if it's closed (see also issue #431) """ # we want to trigger a deeply-nested condition in # processPublish in class Broker -- lets try w/o refactoring # anything first... class TestSession(ApplicationSession): pass session0 = TestSession() session1 = TestSession() router = mock.MagicMock() broker = Broker(router) # let's just "cheat" our way a little to the right state by # injecting our subscription "directly" (e.g. instead of # faking out an entire Subscribe etc. flow # ...so we need _subscriptions_map to have at least one # subscription (our test one) for the topic we'll publish to broker._subscription_map.add_observer(session0, u'test.topic') # simulate the session state we want, which is that a # transport is connected (._transport != None) but there # _session_id *is* None (not joined yet, or left already) self.assertIs(None, session0._session_id) session0._transport = mock.MagicMock() session0._transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') session1._session_id = 1234 # "from" session should look connected + joined session1._transport = mock.MagicMock() session1._transport.channel_id = b'aaaabeef' # here's the main "cheat"; we're faking out the # router.authorize because we need it to callback immediately router.authorize = mock.MagicMock(return_value=txaio.create_future_success(dict(allow=True, cache=False, disclose=True))) # now we scan call "processPublish" such that we get to the # condition we're interested in (this "comes from" session1 # beacuse by default publishes don't go to the same session) pubmsg = message.Publish(123, u'test.topic') broker.processPublish(session1, pubmsg) # neither session should have sent anything on its transport self.assertEquals(session0._transport.method_calls, []) self.assertEquals(session1._transport.method_calls, [])
class TestDealer(unittest.TestCase): """ """ def setUp(self): """ Setup router and router session factories. """ # create a router factory self.router_factory = RouterFactory() # start a realm self.realm = RouterRealm(u'realm-001', {u'name': u'realm1'}) self.router_factory.start_realm(self.realm) # allow everything self.router = self.router_factory.get(u'realm1') self.router.add_role( RouterRoleStaticAuth( self.router, u'test_role', default_permissions={ u'uri': u'com.example.', u'match': u'prefix', u'allow': { u'call': True, u'register': True, u'publish': True, u'subscribe': True, } } ) ) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) def tearDown(self): pass @defer.inlineCallbacks def test_outstanding_invoke(self): """ When a call is pending and the callee goes away, it cancels the in-flight call """ session = mock.Mock() session._realm = u'realm1' self.router.authorize = mock.Mock( return_value=defer.succeed({u'allow': True, u'disclose': True}) ) rap = RouterApplicationSession(session, self.router_factory) rap.send(message.Hello(u"realm1", {u'caller': role.RoleCallerFeatures()})) rap.send(message.Register(1, u'foo')) # we can retrieve the Registration via # session.mock_calls[-1][1][0] if req'd # re-set the authorize, as the Deferred from above is already # used-up and it gets called again to authorize the Call self.router.authorize = mock.Mock( return_value=defer.succeed({u'allow': True, u'disclose': True}) ) rap.send(message.Call(42, u'foo')) orig = rap.send d = defer.Deferred() rap.send(message.Goodbye()) def wrapper(*args, **kw): d.callback(args[0]) return orig(*args, **kw) rap.send = wrapper # we can do this *after* the call to send() the Goodbye # (above) because it takes a reactor-turn to actually # process the cancel/errors etc -- hence the Deferred and # yield in this test... msg = yield d self.assertEqual(42, msg.request) self.assertEqual(u'wamp.error.canceled', msg.error)
class TestBrokerPublish(unittest.TestCase): """ Tests for crossbar.router.broker.Broker """ def setUp(self): """ Setup router and router session factories. """ # create a router factory self.router_factory = RouterFactory(None, None) # start a realm self.realm = RouterRealm(None, {u'name': u'realm1'}) self.router_factory.start_realm(self.realm) # allow everything self.router = self.router_factory.get(u'realm1') self.router.add_role( RouterRoleStaticAuth( self.router, u'test_role', default_permissions={ u'uri': u'com.example.', u'match': u'prefix', u'allow': { u'call': True, u'register': True, u'publish': True, u'subscribe': True, } } ) ) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) def tearDown(self): pass def test_add(self): """ Create an application session and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): txaio.resolve(d, None) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session) return d def test_application_session_internal_error(self): """ simulate an internal error triggering the 'onJoin' error-case from RouterApplicationSession's send() method (from the Hello msg) """ # setup the_exception = RuntimeError("sadness") errors = [] class TestSession(ApplicationSession): def onJoin(self, *args, **kw): raise the_exception def onUserError(self, fail, msg): errors.append((fail, msg)) session = TestSession(types.ComponentConfig(u'realm1')) from crossbar.router.session import RouterApplicationSession # Note to self: original code was logging directly in # RouterApplicationSession -- which *may* actually be better? # or not... with mock.patch.object(RouterApplicationSession, 'log') as logger: # this should call onJoin, triggering our error self.session_factory.add(session) if True: self.assertEqual(1, len(errors), "Didn't see our error") self.assertEqual(the_exception, errors[0][0].value) else: # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertEqual(call[1][0].value, the_exception) def test_router_session_internal_error_onHello(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onHello """ # setup transport = mock.MagicMock() transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory() # __call__ on the _RouterSessionFactory session.onHello = boom session.onOpen(transport) msg = message.Hello(u'realm1', dict(caller=role.RoleCallerFeatures())) # XXX think: why isn't this using _RouterSession.log? from crossbar.router.session import RouterSession with mock.patch.object(RouterSession, 'log') as logger: # do the test; should call onHello which is now "boom", above session.onMessage(msg) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertTrue('failure' in call[2]) self.assertEqual(call[2]['failure'].value, the_exception) def test_router_session_internal_error_onAuthenticate(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onAuthenticate) """ # setup transport = mock.MagicMock() transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory() # __call__ on the _RouterSessionFactory session.onAuthenticate = boom session.onOpen(transport) msg = message.Authenticate(u'bogus signature') # do the test; should call onHello which is now "boom", above session.onMessage(msg) errors = self.flushLoggedErrors() self.assertEqual(1, len(errors), "Expected just one error: {}".format(errors)) self.assertTrue(the_exception in [fail.value for fail in errors]) def test_router_session_goodbye_custom_message(self): """ Reason should be propagated properly from Goodbye message """ from crossbar.router.session import RouterApplicationSession session = mock.Mock() session._realm = u'realm' router_factory = mock.Mock() rap = RouterApplicationSession(session, router_factory) rap.send(message.Hello(u'realm', {u'caller': role.RoleCallerFeatures()})) session.reset_mock() rap.send(message.Goodbye(u'wamp.reason.logout', u'some custom message')) leaves = [call for call in session.mock_calls if call[0] == 'onLeave'] self.assertEqual(1, len(leaves)) details = leaves[0][1][0] self.assertEqual(u'wamp.reason.logout', details.reason) self.assertEqual(u'some custom message', details.message) def test_router_session_goodbye_onLeave_error(self): """ Reason should be propagated properly from Goodbye message """ from crossbar.router.session import RouterApplicationSession session = mock.Mock() the_exception = RuntimeError("onLeave fails") def boom(*args, **kw): raise the_exception session.onLeave = mock.Mock(side_effect=boom) session._realm = u'realm' router_factory = mock.Mock() rap = RouterApplicationSession(session, router_factory) rap.send(message.Hello(u'realm', {u'caller': role.RoleCallerFeatures()})) session.reset_mock() rap.send(message.Goodbye(u'wamp.reason.logout', u'some custom message')) errors = self.flushLoggedErrors() self.assertEqual(1, len(errors)) self.assertEqual(the_exception, errors[0].value) def test_router_session_goodbye_fire_disconnect_error(self): """ Reason should be propagated properly from Goodbye message """ from crossbar.router.session import RouterApplicationSession session = mock.Mock() the_exception = RuntimeError("sad times at ridgemont high") def boom(*args, **kw): if args[0] == 'disconnect': return defer.fail(the_exception) return defer.succeed(None) session.fire = mock.Mock(side_effect=boom) session._realm = u'realm' router_factory = mock.Mock() rap = RouterApplicationSession(session, router_factory) rap.send(message.Hello(u'realm', {u'caller': role.RoleCallerFeatures()})) session.reset_mock() rap.send(message.Goodbye(u'wamp.reason.logout', u'some custom message')) errors = self.flushLoggedErrors() self.assertEqual(1, len(errors)) self.assertEqual(the_exception, errors[0].value) def test_router_session_lifecycle(self): """ We see all 'lifecycle' notifications. """ from crossbar.router.session import RouterApplicationSession def mock_fire(name, *args, **kw): fired.append(name) return defer.succeed(None) fired = [] session = mock.Mock() session._realm = u'realm' session.fire = mock.Mock(side_effect=mock_fire) router_factory = mock.Mock() rap = RouterApplicationSession(session, router_factory) # we never fake out the 'Welcome' message, so there will be no # 'ready' notification... rap.send(message.Hello(u'realm', {u'caller': role.RoleCallerFeatures()})) rap.send(message.Goodbye(u'wamp.reason.logout', u'some custom message')) self.assertTrue('connect' in fired) self.assertTrue('join' in fired) self.assertTrue('ready' in fired) self.assertTrue('leave' in fired) self.assertTrue('disconnect' in fired) def test_add_and_subscribe(self): """ Create an application session that subscribes to some topic and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): d2 = self.subscribe(lambda: None, u'com.example.topic1') def ok(_): txaio.resolve(d, None) def error(err): txaio.reject(d, err) txaio.add_callbacks(d2, ok, error) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session, authrole=u'test_role') return d def test_publish_closed_session(self): """ ensure a session doesn't get Events if it's closed (see also issue #431) """ # we want to trigger a deeply-nested condition in # processPublish in class Broker -- lets try w/o refactoring # anything first... class TestSession(ApplicationSession): pass session0 = TestSession() session1 = TestSession() router = mock.MagicMock() router.new_correlation_id = lambda: u'fake correlation id' broker = Broker(router, reactor) # let's just "cheat" our way a little to the right state by # injecting our subscription "directly" (e.g. instead of # faking out an entire Subscribe etc. flow # ...so we need _subscriptions_map to have at least one # subscription (our test one) for the topic we'll publish to broker._subscription_map.add_observer(session0, u'test.topic') # simulate the session state we want, which is that a # transport is connected (._transport != None) but there # _session_id *is* None (not joined yet, or left already) self.assertIs(None, session0._session_id) session0._transport = mock.MagicMock() session0._transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') session1._session_id = 1234 # "from" session should look connected + joined session1._transport = mock.MagicMock() session1._transport.channel_id = b'aaaabeef' # here's the main "cheat"; we're faking out the # router.authorize because we need it to callback immediately router.authorize = mock.MagicMock(return_value=txaio.create_future_success(dict(allow=True, cache=False, disclose=True))) # now we scan call "processPublish" such that we get to the # condition we're interested in (this "comes from" session1 # beacuse by default publishes don't go to the same session) pubmsg = message.Publish(123, u'test.topic') broker.processPublish(session1, pubmsg) # neither session should have sent anything on its transport self.assertEquals(session0._transport.method_calls, []) self.assertEquals(session1._transport.method_calls, []) def test_publish_traced_events(self): """ with two subscribers and message tracing the last event should have a magic flag """ # we want to trigger a deeply-nested condition in # processPublish in class Broker -- lets try w/o refactoring # anything first... class TestSession(ApplicationSession): pass session0 = TestSession() session1 = TestSession() session2 = TestSession() router = mock.MagicMock() router.send = mock.Mock() router.new_correlation_id = lambda: u'fake correlation id' router.is_traced = True broker = Broker(router, reactor) # let's just "cheat" our way a little to the right state by # injecting our subscription "directly" (e.g. instead of # faking out an entire Subscribe etc. flow # ...so we need _subscriptions_map to have at least one # subscription (our test one) for the topic we'll publish to broker._subscription_map.add_observer(session0, u'test.topic') broker._subscription_map.add_observer(session1, u'test.topic') session0._session_id = 1000 session0._transport = mock.MagicMock() session0._transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') session1._session_id = 1001 session1._transport = mock.MagicMock() session1._transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') session2._session_id = 1002 session2._transport = mock.MagicMock() session2._transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') # here's the main "cheat"; we're faking out the # router.authorize because we need it to callback immediately router.authorize = mock.MagicMock(return_value=txaio.create_future_success(dict(allow=True, cache=False, disclose=True))) # now we scan call "processPublish" such that we get to the # condition we're interested in (this "comes from" session1 # beacuse by default publishes don't go to the same session) pubmsg = message.Publish(123, u'test.topic') broker.processPublish(session2, pubmsg) # extract all the event calls events = [ call[1][1] for call in router.send.mock_calls if call[1][0] in [session0, session1, session2] ] self.assertEqual(2, len(events)) self.assertFalse(events[0].correlation_is_last) self.assertTrue(events[1].correlation_is_last) def test_publish_traced_events_batched(self): """ with two subscribers and message tracing the last event should have a magic flag """ # we want to trigger a deeply-nested condition in # processPublish in class Broker -- lets try w/o refactoring # anything first... class TestSession(ApplicationSession): pass session0 = TestSession() session1 = TestSession() session2 = TestSession() session3 = TestSession() session4 = TestSession() # NOTE! We ensure that "session0" (the publishing session) is # *last* in the observation-list to trigger a (now fixed) # edge-case) sessions = [session1, session2, session3, session4, session0] router = mock.MagicMock() router.send = mock.Mock() router.new_correlation_id = lambda: u'fake correlation id' router.is_traced = True clock = Clock() with replace_loop(clock): broker = Broker(router, clock) broker._options.event_dispatching_chunk_size = 2 # to ensure we get "session0" last, we turn on ordering in # the observations broker._subscription_map._ordered = 1 # let's just "cheat" our way a little to the right state by # injecting our subscription "directly" (e.g. instead of # faking out an entire Subscribe etc. flow # ...so we need _subscriptions_map to have at least one # subscription (our test one) for the topic we'll publish to for session in sessions: broker._subscription_map.add_observer(session, u'test.topic') for i, sess in enumerate(sessions): sess._session_id = 1000 + i sess._transport = mock.MagicMock() sess._transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') # here's the main "cheat"; we're faking out the # router.authorize because we need it to callback immediately router.authorize = mock.MagicMock(return_value=txaio.create_future_success(dict(allow=True, cache=False, disclose=True))) # now we scan call "processPublish" such that we get to the # condition we're interested in; should go to all sessions # except session0 pubmsg = message.Publish(123, u'test.topic') broker.processPublish(session0, pubmsg) clock.advance(1) clock.advance(1) # extract all the event calls events = [ call[1][1] for call in router.send.mock_calls if call[1][0] in [session0, session1, session2, session3, session4] ] # all except session0 should have gotten an event, and # session4's should have the "last" flag set self.assertEqual(4, len(events)) self.assertFalse(events[0].correlation_is_last) self.assertFalse(events[1].correlation_is_last) self.assertFalse(events[2].correlation_is_last) self.assertTrue(events[3].correlation_is_last)
class TestEmbeddedSessions(unittest.TestCase): """ Test cases for application session running embedded in router. """ def setUp(self): """ Setup router and router session factories. """ # create a router factory self.router_factory = RouterFactory() # start a realm self.router_factory.start_realm(RouterRealm(None, {u'name': u'realm1'})) # allow everything permissions = RouterPermissions('', True, True, True, True, True) router = self.router_factory.get(u'realm1') router.add_role(RouterRoleStaticAuth(router, None, default_permissions=permissions)) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) def tearDown(self): pass def test_add(self): """ Create an application session and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): txaio.resolve(d, None) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session) return d def test_add_and_subscribe(self): """ Create an application session that subscribes to some topic and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): # noinspection PyUnusedLocal def on_event(*arg, **kwargs): pass d2 = self.subscribe(on_event, u'com.example.topic1') def ok(_): txaio.resolve(d, None) def error(err): txaio.reject(d, err) txaio.add_callbacks(d2, ok, error) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session) return d
class CallerTestCase(TestCase): """ Unit tests for L{CallerResource}. """ def setUp(self): # create a router factory self.router_factory = RouterFactory(None, None) # start a realm self.realm = RouterRealm(None, {u'name': u'realm1'}) self.router_factory.start_realm(self.realm) # allow everything self.router = self.router_factory.get(u'realm1') self.router.add_role( RouterRoleStaticAuth(self.router, u'test_role', default_permissions={ u'uri': u'com.myapp.', u'match': u'prefix', u'allow': { u'call': True, u'register': True, u'publish': True, u'subscribe': True, } })) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) @inlineCallbacks def test_add2(self): """ Test a very basic call where you square root a number. This has one arg, no kwargs, and no authorisation. """ session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session, authrole=u"test_role") session2 = ApplicationSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session2, authrole=u"test_role") resource = CallerResource({}, session2) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b'{"procedure": "com.myapp.sqrt", "args": [2]}') self.assertEqual(request.code, 200) self.assertEqual(json.loads(native_string(request.get_written_data())), {"args": [1.4142135623730951]}) logs = l.get_category("AR202") self.assertEqual(len(logs), 1) self.assertEqual(logs[0]["code"], 200) @inlineCallbacks def test_failure(self): """ A failed call returns the error to the client. """ session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session, authrole=u"test_role") session2 = ApplicationSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session2, authrole=u"test_role") resource = CallerResource({}, session2) tests = [ (u"com.myapp.sqrt", (0, ), { u"error": u"wamp.error.runtime_error", u"args": [u"don't ask foolish questions ;)"], u"kwargs": {} }), (u"com.myapp.checkname", ("foo", ), { u"error": u"com.myapp.error.reserved", u"args": [], u"kwargs": {} }), (u"com.myapp.checkname", ("*", ), { u"error": u"com.myapp.error.invalid_length", u"args": [], u"kwargs": { "min": 3, "max": 10 } }), (u"com.myapp.checkname", ("hello", ), { u"error": u"com.myapp.error.mixed_case", u"args": ["hello", "HELLO"], u"kwargs": {} }), (u"com.myapp.compare", (1, 10), { u"error": u"com.myapp.error1", u"args": [9], u"kwargs": {} }), ] for procedure, args, err in tests: with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=dump_json({ "procedure": procedure, "args": args }).encode('utf8')) self.assertEqual(request.code, 200) self.assertEqual( json.loads(native_string(request.get_written_data())), err) logs = l.get_category("AR458") self.assertEqual(len(logs), 1) self.assertEqual(logs[0]["code"], 200) # We manually logged the errors; we can flush them from the log self.flushLoggedErrors() @inlineCallbacks def test_cb_failure(self): """ Test that calls with no procedure in the request body are rejected. """ resource = CallerResource({}, None) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b'{"procedure": "foo"}') self.assertEqual(request.code, 500) self.assertEqual( json.loads(native_string(request.get_written_data())), { "error": "wamp.error.runtime_error", "args": ["Sorry, Crossbar.io has encountered a problem."], "kwargs": {} }) errors = l.get_category("AR500") self.assertEqual(len(errors), 1) # We manually logged the errors; we can flush them from the log self.flushLoggedErrors() @inlineCallbacks def test_no_procedure(self): """ Test that calls with no procedure in the request body are rejected. """ resource = CallerResource({}, None) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b"{}") self.assertEqual(request.code, 400) errors = l.get_category("AR455") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400) @inlineCallbacks def test_no_body(self): """ Test that calls with no body are rejected. """ resource = CallerResource({}, None) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}) self.assertEqual(request.code, 400) errors = l.get_category("AR453") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)
class TestEmbeddedSessions(unittest.TestCase): """ Test cases for application session running embedded in router. """ def setUp(self): """ Setup router and router session factories. """ # create a router factory self.router_factory = RouterFactory(u'mynode') # start a realm self.router_factory.start_realm(RouterRealm(None, {u'name': u'realm1'})) # allow everything default_permissions = { u'uri': u'', u'match': u'prefix', u'allow': { u'call': True, u'register': True, u'publish': True, u'subscribe': True } } self.router = self.router_factory.get(u'realm1') self.router.add_role(RouterRoleStaticAuth(self.router, u'test_role', default_permissions=default_permissions)) self.router.add_role(RouterRoleStaticAuth(self.router, None, default_permissions=default_permissions)) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) def tearDown(self): pass def test_authorize_exception_call(self): """ When a dynamic authorizor throws an exception (during processCall) we log it. """ the_exception = RuntimeError("authorizer bug") def boom(*args, **kw): raise the_exception self.router._roles[u'test_role'].authorize = boom class TestSession(ApplicationSession): def __init__(self, *args, **kw): super(TestSession, self).__init__(*args, **kw) self._authrole = u'test_role' self._transport = mock.MagicMock() session0 = TestSession() self.router._dealer._registration_map.add_observer(session0, u'test.proc') # okay, we have an authorizer that will always explode and a # single procedure registered; when we call it, then # on_authorize_error (in dealer.py) should get called and our # error logged. call = message.Call( request=1234, procedure=u'test.proc', args=tuple(), kwargs=dict(), ) # this should produce an error -- however processCall doesn't # itself return the Deferred, so we look for the side-effect # -- the router should have tried to send a message.Error (and # we should also have logged the error). self.router._dealer.processCall(session0, call) self.assertEqual(1, len(session0._transport.mock_calls)) call = session0._transport.mock_calls[0] self.assertEqual('send', call[0]) # ensure we logged our error (flushLoggedErrors also causes # trial to *not* fail the unit-test despite an error logged) errors = self.flushLoggedErrors() self.assertTrue(the_exception in [fail.value for fail in errors]) def test_authorize_exception_register(self): """ When a dynamic authorizor throws an exception (during processRegister) we log it. """ the_exception = RuntimeError("authorizer bug") def boom(*args, **kw): raise the_exception self.router._roles[u'test_role'].authorize = boom class TestSession(ApplicationSession): def __init__(self, *args, **kw): super(TestSession, self).__init__(*args, **kw) self._authrole = u'test_role' self._transport = mock.MagicMock() session0 = TestSession() call = message.Register( request=1234, procedure=u'test.proc_reg', ) # this should produce an error -- however processCall doesn't # itself return the Deferred, so we look for the side-effect # -- the router should have tried to send a message.Error (and # we should also have logged the error). self.router._dealer.processRegister(session0, call) self.assertEqual(1, len(session0._transport.mock_calls)) call = session0._transport.mock_calls[0] self.assertEqual('send', call[0]) # ensure we logged our error (flushLoggedErrors also causes # trial to *not* fail the unit-test despite an error logged) errors = self.flushLoggedErrors() self.assertTrue(the_exception in [fail.value for fail in errors]) def test_add(self): """ Create an application session and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): txaio.resolve(d, None) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session) return d def test_application_session_internal_error(self): """ simulate an internal error triggering the 'onJoin' error-case from _RouterApplicationSession's send() method (from the Hello msg) """ # setup the_exception = RuntimeError("sadness") errors = [] class TestSession(ApplicationSession): def onJoin(self, *args, **kw): raise the_exception def onUserError(self, *args, **kw): errors.append((args, kw)) session = TestSession(types.ComponentConfig(u'realm1')) # in this test, we are just looking for onUserError to get # called so we don't need to patch the logger. this should # call onJoin, triggering our error self.session_factory.add(session) # check we got the right log.failure() call self.assertTrue(len(errors) > 0, "expected onUserError call") fail = errors[0][0][0] self.assertTrue(fail.value == the_exception) def test_router_session_internal_error_onHello(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onHello """ # setup transport = mock.MagicMock() transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory() # __call__ on the _RouterSessionFactory session.onHello = boom session.onOpen(transport) msg = message.Hello(u'realm1', dict(caller=role.RoleCallerFeatures())) # XXX think: why isn't this using _RouterSession.log? from crossbar.router.session import RouterSession with mock.patch.object(RouterSession, 'log') as logger: # do the test; should call onHello which is now "boom", above session.onMessage(msg) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertTrue('failure' in call[2]) self.assertEqual(call[2]['failure'].value, the_exception) def test_router_session_internal_error_onAuthenticate(self): """ similar to above, but during _RouterSession's onMessage handling, where it calls self.onAuthenticate) """ # setup transport = mock.MagicMock() transport.get_channel_id = mock.MagicMock(return_value=b'deadbeef') the_exception = RuntimeError("kerblam") def boom(*args, **kw): raise the_exception session = self.session_factory() # __call__ on the _RouterSessionFactory session.onAuthenticate = boom session.onOpen(transport) msg = message.Authenticate(u'bogus signature') # XXX think: why isn't this using _RouterSession.log? from crossbar.router.session import RouterSession with mock.patch.object(RouterSession, 'log') as logger: # do the test; should call onHello which is now "boom", above session.onMessage(msg) # check we got the right log.failure() call self.assertTrue(len(logger.method_calls) > 0) call = logger.method_calls[0] # for a MagicMock call-object, 0th thing is the method-name, 1st # thing is the arg-tuple, 2nd thing is the kwargs. self.assertEqual(call[0], 'failure') self.assertTrue('failure' in call[2]) self.assertEqual(call[2]['failure'].value, the_exception) def test_add_and_subscribe(self): """ Create an application session that subscribes to some topic and add it to a router to run embedded. """ d = txaio.create_future() class TestSession(ApplicationSession): def onJoin(self, details): # noinspection PyUnusedLocal def on_event(*arg, **kwargs): pass d2 = self.subscribe(on_event, u'com.example.topic1') def ok(_): txaio.resolve(d, None) def error(err): txaio.reject(d, err) txaio.add_callbacks(d2, ok, error) session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session) return d
class CallerTestCase(TestCase): """ Unit tests for L{CallerResource}. """ def setUp(self): # create a router factory self.router_factory = RouterFactory(None, None) # start a realm self.realm = RouterRealm(None, {u'name': u'realm1'}) self.router_factory.start_realm(self.realm) # allow everything self.router = self.router_factory.get(u'realm1') self.router.add_role( RouterRoleStaticAuth( self.router, u'test_role', default_permissions={ u'uri': u'com.myapp.', u'match': u'prefix', u'allow': { u'call': True, u'register': True, u'publish': True, u'subscribe': True, } } ) ) # create a router session factory self.session_factory = RouterSessionFactory(self.router_factory) @inlineCallbacks def test_add2(self): """ Test a very basic call where you square root a number. This has one arg, no kwargs, and no authorisation. """ session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session, authrole=u"test_role") session2 = ApplicationSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session2, authrole=u"test_role") resource = CallerResource({}, session2) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b'{"procedure": "com.myapp.sqrt", "args": [2]}') self.assertEqual(request.code, 200) self.assertEqual(json.loads(native_string(request.get_written_data())), {"args": [1.4142135623730951]}) logs = l.get_category("AR202") self.assertEqual(len(logs), 1) self.assertEqual(logs[0]["code"], 200) @inlineCallbacks def test_failure(self): """ A failed call returns the error to the client. """ session = TestSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session, authrole=u"test_role") session2 = ApplicationSession(types.ComponentConfig(u'realm1')) self.session_factory.add(session2, authrole=u"test_role") resource = CallerResource({}, session2) tests = [ (u"com.myapp.sqrt", (0,), {u"error": u"wamp.error.runtime_error", u"args": [u"don't ask foolish questions ;)"], u"kwargs": {}}), (u"com.myapp.checkname", ("foo",), {u"error": u"com.myapp.error.reserved", u"args": [], u"kwargs": {}}), (u"com.myapp.checkname", ("*",), {u"error": u"com.myapp.error.invalid_length", u"args": [], u"kwargs": {"min": 3, "max": 10}}), (u"com.myapp.checkname", ("hello",), {u"error": u"com.myapp.error.mixed_case", u"args": ["hello", "HELLO"], u"kwargs": {}}), (u"com.myapp.compare", (1, 10), {u"error": u"com.myapp.error1", u"args": [9], u"kwargs": {}}), ] for procedure, args, err in tests: with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=dump_json({"procedure": procedure, "args": args}).encode('utf8')) self.assertEqual(request.code, 200) self.assertEqual(json.loads(native_string(request.get_written_data())), err) logs = l.get_category("AR458") self.assertEqual(len(logs), 1) self.assertEqual(logs[0]["code"], 200) # We manually logged the errors; we can flush them from the log self.flushLoggedErrors() @inlineCallbacks def test_cb_failure(self): """ Test that calls with no procedure in the request body are rejected. """ resource = CallerResource({}, None) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b'{"procedure": "foo"}') self.assertEqual(request.code, 500) self.assertEqual(json.loads(native_string(request.get_written_data())), {"error": "wamp.error.runtime_error", "args": ["Sorry, Crossbar.io has encountered a problem."], "kwargs": {}}) errors = l.get_category("AR500") self.assertEqual(len(errors), 1) # We manually logged the errors; we can flush them from the log self.flushLoggedErrors() @inlineCallbacks def test_no_procedure(self): """ Test that calls with no procedure in the request body are rejected. """ resource = CallerResource({}, None) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b"{}") self.assertEqual(request.code, 400) errors = l.get_category("AR455") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400) @inlineCallbacks def test_no_body(self): """ Test that calls with no body are rejected. """ resource = CallerResource({}, None) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}) self.assertEqual(request.code, 400) errors = l.get_category("AR453") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)