def test_get_info_address_caching(self, msg_mock): msg_mock.return_value = ControlMessage.from_str('551 Address unknown\r\n') self.assertEqual(None, self.controller._last_address_exc) self.assertRaisesWith(stem.OperationFailed, 'Address unknown', self.controller.get_info, 'address') self.assertEqual('Address unknown', str(self.controller._last_address_exc)) self.assertEqual(1, msg_mock.call_count) # now that we have a cached failure we should provide that back self.assertRaisesWith(stem.OperationFailed, 'Address unknown', self.controller.get_info, 'address') self.assertEqual(1, msg_mock.call_count) # invalidates the cache, transitioning from no address to having one msg_mock.return_value = ControlMessage.from_str('250-address=17.2.89.80\r\n250 OK\r\n', 'GETINFO') self.assertRaisesWith(stem.OperationFailed, 'Address unknown', self.controller.get_info, 'address') self.controller._handle_event(ControlMessage.from_str('650 STATUS_SERVER NOTICE EXTERNAL_ADDRESS ADDRESS=17.2.89.80 METHOD=DIRSERV\r\n')) self.assertEqual('17.2.89.80', self.controller.get_info('address')) # invalidates the cache, transitioning from one address to another msg_mock.return_value = ControlMessage.from_str('250-address=80.89.2.17\r\n250 OK\r\n', 'GETINFO') self.assertEqual('17.2.89.80', self.controller.get_info('address')) self.controller._handle_event(ControlMessage.from_str('650 STATUS_SERVER NOTICE EXTERNAL_ADDRESS ADDRESS=80.89.2.17 METHOD=DIRSERV\r\n')) self.assertEqual('80.89.2.17', self.controller.get_info('address'))
def test_invalid_requests(self): """ Parses a MAPADDRESS replies that contain an error code due to hostname syntax errors. """ control_message = ControlMessage.from_str(UNRECOGNIZED_KEYS_RESPONSE, normalize = True) self.assertRaises(stem.InvalidRequest, stem.response.convert, 'MAPADDRESS', control_message) control_message = ControlMessage.from_str(PARTIAL_FAILURE_RESPONSE, 'MAPADDRESS', normalize = True) self.assertEqual({'23': '324'}, control_message.entries)
def test_single_line_response(self): message = ControlMessage.from_str('552 NOTOK\r\n', 'SINGLELINE') self.assertEqual(False, message.is_ok()) message = ControlMessage.from_str('250 KK\r\n', 'SINGLELINE') self.assertEqual(True, message.is_ok()) message = ControlMessage.from_str('250 OK\r\n', 'SINGLELINE') self.assertEqual(True, message.is_ok(True)) message = ControlMessage.from_str('250 HMM\r\n', 'SINGLELINE') self.assertEqual(False, message.is_ok(True))
def test_invalid_response(self): """ Parses a malformed MAPADDRESS reply that contains an invalid response code. This is a proper controller message, but malformed according to the MAPADDRESS's spec. """ control_message = ControlMessage.from_str(INVALID_EMPTY_RESPONSE, normalize = True) self.assertRaises(stem.ProtocolError, stem.response.convert, 'MAPADDRESS', control_message) control_message = ControlMessage.from_str(INVALID_RESPONSE, normalize = True) self.assertRaises(stem.ProtocolError, stem.response.convert, 'MAPADDRESS', control_message)
def test_invalid_requests(self): """ Parses a MAPADDRESS replies that contain an error code due to hostname syntax errors. """ control_message = ControlMessage.from_str(UNRECOGNIZED_KEYS_RESPONSE, normalize=True) self.assertRaises(stem.InvalidRequest, stem.response.convert, 'MAPADDRESS', control_message) control_message = ControlMessage.from_str(PARTIAL_FAILURE_RESPONSE, 'MAPADDRESS', normalize=True) self.assertEqual({'23': '324'}, control_message.mapped)
def test_single_response(self): """ Parses a GETCONF reply response for a single parameter. """ control_message = ControlMessage.from_str(SINGLE_RESPONSE, 'GETCONF', normalize = True) self.assertEqual({'DataDirectory': ['/home/neena/.tor']}, control_message.entries)
def test_get_protocolinfo(self, get_protocolinfo_mock): """ Exercises the get_protocolinfo() method. """ # use the handy mocked protocolinfo response protocolinfo_msg = ControlMessage.from_str('250-PROTOCOLINFO 1\r\n250 OK\r\n', 'PROTOCOLINFO') get_protocolinfo_mock.return_value = protocolinfo_msg # compare the str representation of these object, because the class # does not have, nor need, a direct comparison operator self.assertEqual( str(protocolinfo_msg), str(self.controller.get_protocolinfo()) ) # raise an exception in the stem.connection.get_protocolinfo() call get_protocolinfo_mock.side_effect = ProtocolError # get a default value when the call fails self.assertEqual( 'default returned', self.controller.get_protocolinfo(default = 'default returned') ) # no default value, accept the error self.assertRaises(ProtocolError, self.controller.get_protocolinfo)
def test_no_key_type(self): """ Checks a response that's missing the private key type. """ response = ControlMessage.from_str(MISSING_KEY_TYPE, normalize = True) self.assertRaisesRegexp(stem.ProtocolError, 'ADD_ONION PrivateKey lines should be of the form', stem.response.convert, 'ADD_ONION', response)
def test_without_service_id(self): """ Checks a response that lack an initial service id. """ response = ControlMessage.from_str(WRONG_FIRST_KEY, normalize = True) self.assertRaisesRegexp(stem.ProtocolError, 'ADD_ONION response should start with', stem.response.convert, 'ADD_ONION', response)
def __parse_line(self, line): if not self.boot_succeeded: if re.search("Starting\storctl\sprogram\son\shost", line) is not None: parts = line.strip().split() if len(parts) < 11: return True self.name = parts[10] if re.search("Bootstrapped\s100", line) is not None: self.boot_succeeded = True elif re.search("BOOTSTRAP", line) is not None and re.search("PROGRESS=100", line) is not None: self.boot_succeeded = True if self.do_simple is False or (self.do_simple is True and re.search("650\sBW", line) is not None): # parse with stem timestamps, sep, raw_event_str = line.partition(" 650 ") if sep == '': return True # event.arrived_at is also available but at worse granularity unix_ts = float(timestamps.strip().split()[2]) # check if we should ignore the line line_date = datetime.datetime.utcfromtimestamp(unix_ts).date() if not self.__is_date_valid(line_date): return True event = ControlMessage.from_str("{0} {1}".format(sep.strip(), raw_event_str)) convert('EVENT', event) self.__handle_event(event, unix_ts) return True
def __parse_line(self, line): if not self.boot_succeeded: if re.search("Starting\storctl\sprogram\son\shost", line) is not None: parts = line.strip().split() if len(parts) < 11: return True self.name = parts[10] if re.search("Bootstrapped\s100", line) is not None: self.boot_succeeded = True elif re.search("BOOTSTRAP", line) is not None and re.search( "PROGRESS=100", line) is not None: self.boot_succeeded = True # parse with stem timestamps, sep, raw_event_str = line.partition(" 650 ") if sep == '': return True # event.arrived_at is also available but at worse granularity unix_ts = float(timestamps.strip().split()[2]) # check if we should ignore the line line_date = datetime.datetime.utcfromtimestamp(unix_ts).date() if not self.__is_date_valid(line_date): return True event = ControlMessage.from_str("{0} {1}".format( sep.strip(), raw_event_str)) convert('EVENT', event) self.__handle_event(event, unix_ts) return True
def test_password_auth(self): """ Checks a response with password authentication. """ control_message = ControlMessage.from_str(PASSWORD_AUTH, 'PROTOCOLINFO', normalize = True) self.assertEqual((AuthMethod.PASSWORD, ), control_message.auth_methods)
def built_circ(circ_id, purpose, guard="$5416F3E8F80101A133B1970495B04FDBD1C7446B~Unnamed"): s = "650 CIRC " + str( circ_id ) + " BUILT " + guard + ",$1F9544C0A80F1C5D8A5117FBFFB50694469CC7F4~as44194l10501,$DBD67767640197FF96EC6A87684464FC48F611B6~nocabal,$387B065A38E4DAA16D9D41C2964ECBC4B31D30FF~redjohn1 BUILD_FLAGS=IS_INTERNAL,NEED_CAPACITY,NEED_UPTIME PURPOSE=" + purpose + " TIME_CREATED=2018-05-04T06:09:32.751920\r\n" return ControlMessage.from_str(s, "EVENT")
def test_setevents(self): controller = Mock() controller.msg.return_value = ControlMessage.from_str('250 OK\r\n') interpreter = ControlInterpreter(controller) self.assertEqual('\x1b[34m250 OK\x1b[0m\n', interpreter.run_command('SETEVENTS BW'))
def test_single_response(self): """ Parses a GETINFO reply response for a single parameter. """ control_message = ControlMessage.from_str(SINGLE_RESPONSE, 'GETINFO', normalize = True) self.assertEqual({'version': b'0.2.3.11-alpha-dev'}, control_message.entries)
def test_single_response(self): """ Parses a MAPADDRESS reply response with a single address mapping. """ control_message = ControlMessage.from_str('250 foo=bar\r\n', 'MAPADDRESS') self.assertEqual({'foo': 'bar'}, control_message.entries)
def test_get_info_without_fingerprint(self, get_conf_mock, msg_mock): msg_mock.return_value = ControlMessage.from_str( '551 Not running in server mode\r\n') get_conf_mock.return_value = None self.assertEqual(None, self.controller._last_fingerprint_exc) self.assertRaisesWith(stem.OperationFailed, 'Not running in server mode', self.controller.get_info, 'fingerprint') self.assertEqual('Not running in server mode', str(self.controller._last_fingerprint_exc)) self.assertEqual(1, msg_mock.call_count) # now that we have a cached failure we should provide that back self.assertRaisesWith(stem.OperationFailed, 'Not running in server mode', self.controller.get_info, 'fingerprint') self.assertEqual(1, msg_mock.call_count) # ... but if we become a relay we'll call it again get_conf_mock.return_value = '443' self.assertRaisesWith(stem.OperationFailed, 'Not running in server mode', self.controller.get_info, 'fingerprint') self.assertEqual(2, msg_mock.call_count)
async def test_with_get_protocolinfo(self, authenticate_none_mock, get_protocolinfo_mock): """ Tests the authenticate() function when it needs to make a get_protocolinfo. """ # tests where get_protocolinfo succeeds authenticate_none_mock.side_effect = coro_func_returning_value(None) protocolinfo_message = ControlMessage.from_str( '250-PROTOCOLINFO 1\r\n250 OK\r\n', 'PROTOCOLINFO') protocolinfo_message.auth_methods = (stem.connection.AuthMethod.NONE, ) get_protocolinfo_mock.side_effect = coro_func_returning_value( protocolinfo_message) await stem.connection.authenticate(None) # tests where get_protocolinfo raises an exception get_protocolinfo_mock.side_effect = stem.ProtocolError with self.assertRaises(stem.connection.IncorrectSocketType): await stem.connection.authenticate(None) get_protocolinfo_mock.side_effect = stem.SocketError with self.assertRaises(stem.connection.AuthenticationFailure): await stem.connection.authenticate(None)
def _emit_event(self, event): # Spins up our Controller's thread pool, emits an event, then shuts it # down. This last part is important for a couple reasons... # # 1. So we don't leave any lingering threads. # # 2. To ensure our event handlers are done being executed. Events are # processed asynchronously, so the only way to endsure it's done # with its work is to join on the thread. with patch('time.time', Mock(return_value=TEST_TIMESTAMP)): with patch('stem.control.Controller.is_alive') as is_alive_mock: is_alive_mock.return_value = True self.controller._launch_threads() try: # Converting an event back into an uncast ControlMessage, then feeding it # into our controller's event queue. uncast_event = ControlMessage.from_str(event.raw_content()) self.controller._event_queue.put(uncast_event) self.controller._event_notice.set() self.controller._event_queue.join( ) # block until the event is consumed finally: is_alive_mock.return_value = False self.controller._close()
def test_invalid_response(self): """ Parses a malformed MAPADDRESS reply that contains an invalid response code. This is a proper controller message, but malformed according to the MAPADDRESS's spec. """ control_message = ControlMessage.from_str(INVALID_EMPTY_RESPONSE, normalize=True) self.assertRaises(stem.ProtocolError, stem.response.convert, 'MAPADDRESS', control_message) control_message = ControlMessage.from_str(INVALID_RESPONSE, normalize=True) self.assertRaises(stem.ProtocolError, stem.response.convert, 'MAPADDRESS', control_message)
def test_convert(self): """ Exercises functionality of the convert method both when it works and there's an error. """ # working case control_message = ControlMessage.from_str(NO_AUTH, 'PROTOCOLINFO', normalize=True) # now this should be a ProtocolInfoResponse (ControlMessage subclass) self.assertTrue( isinstance(control_message, stem.response.ControlMessage)) self.assertTrue( isinstance(control_message, stem.response.protocolinfo.ProtocolInfoResponse)) # exercise some of the ControlMessage functionality raw_content = (NO_AUTH + '\n').replace('\n', '\r\n') self.assertEqual(raw_content, control_message.raw_content()) self.assertTrue(str(control_message).startswith('PROTOCOLINFO 1')) # attempt to convert the wrong type self.assertRaises(TypeError, stem.response.convert, 'PROTOCOLINFO', 'hello world') # attempt to convert a different message type self.assertRaises(stem.ProtocolError, ControlMessage.from_str, '650 BW 32326 2856\r\n', 'PROTOCOLINFO')
def test_get_info_without_fingerprint(self, get_conf_mock, msg_mock): msg_mock.return_value = ControlMessage.from_str( '551 Not running in server mode\r\n') get_conf_mock.return_value = None self.assertEqual(None, self.controller._last_fingerprint_exc) self.assertRaisesRegexp(stem.ProtocolError, 'Not running in server mode', self.controller.get_info, 'fingerprint') self.assertEqual( "GETINFO response didn't have an OK status:\nNot running in server mode", str(self.controller._last_fingerprint_exc)) self.assertEqual(1, msg_mock.call_count) # now that we have a cached failure we should provide that back self.assertRaisesRegexp(stem.ProtocolError, 'Not running in server mode', self.controller.get_info, 'fingerprint') self.assertEqual(1, msg_mock.call_count) # ... but if we become a relay we'll call it again get_conf_mock.return_value = '443' self.assertRaisesRegexp(stem.ProtocolError, 'Not running in server mode', self.controller.get_info, 'fingerprint') self.assertEqual(2, msg_mock.call_count)
def extended_circ(circ_id, purpose, guard="$5416F3E8F80101A133B1970495B04FDBD1C7446B~Unnamed"): s = "650 CIRC " + str( circ_id ) + " EXTENDED " + guard + ",$1F9544C0A80F1C5D8A5117FBFFB50694469CC7F4~as44194l10501,$DBD67767640197FF96EC6A87684464FC48F611B6~nocabal BUILD_FLAGS=IS_INTERNAL,NEED_CAPACITY,NEED_UPTIME PURPOSE=" + purpose + " TIME_CREATED=2018-05-04T06:09:32.751920\r\n" return ControlMessage.from_str(s, "EVENT")
def test_exit_used(self, stdout_mock, from_port_mock): path_1 = ('9EA317EECA56BDF30CAEB208A253FB456EDAB1A0', 'bolobolo1') path_2 = ('00C2C2A16AEDB51D5E5FB7D6168FC66B343D822F', 'ph3x') path_3 = ('A59E1E7C7EAEE083D756EE1FF6EC31CA3D8651D7', 'chaoscomputerclub19') event = ControlMessage.from_str( '650 STREAM 15 SUCCEEDED 3 64.15.112.44:80', 'EVENT', normalize=True) r_line = '%s pZ4efH6u4IPXVu4f9uwxyj2GUdc= oQZFLYe9e4A7bOkWKR7TaNxb0JE 2012-08-06 11:19:31 31.172.30.2 443 0' controller = from_port_mock().__enter__() controller.get_circuit.return_value = _make_circ_event( 1, path_1, path_2, path_3) controller.get_network_status.return_value = RouterStatusEntryV3.create( {'r': r_line % path_3[1]}) controller.get_info.return_value = 'unknown' import exit_used with patch( 'builtins.input', Mock(side_effect=functools.partial(exit_used.stream_event, controller, event))): exit_used.main() self.assertEqual(EXPECTED_EXIT_USED, stdout_mock.getvalue())
def test_unicode_cookie(self): """ Checks an authentication cookie with a unicode path. """ control_message = ControlMessage.from_str(UNICODE_COOKIE_PATH, 'PROTOCOLINFO', normalize = True) self.assertEqual(EXPECTED_UNICODE_PATH, control_message.cookie_path)
def built_hs_circ(circ_id, purpose, hs_state, guard="$5416F3E8F80101A133B1970495B04FDBD1C7446B~Unnamed"): s = "650 CIRC " + str( circ_id ) + " BUILT " + guard + ",$855BC2DABE24C861CD887DB9B2E950424B49FC34~Logforme,$E8B3796C809853D9C8AF6B8EDE9080B6F2AE8005~BensTorRelay,$EAB114DAF0488F1223FF30778468E272E00EDC32~trnyc3 BUILD_FLAGS=IS_INTERNAL,NEED_CAPACITY,NEED_UPTIME PURPOSE=" + purpose + " HS_STATE=" + hs_state + " REND_QUERY=4u56zw2g4uvyyq7i TIME_CREATED=2018-05-04T05:50:41.751938\r\n" return ControlMessage.from_str(s, "EVENT")
def test_unknown_auth(self): """ Checks a response with an unrecognized authtentication method. """ control_message = ControlMessage.from_str(UNKNOWN_AUTH, 'PROTOCOLINFO', normalize = True) self.assertEqual((AuthMethod.UNKNOWN, AuthMethod.PASSWORD), control_message.auth_methods) self.assertEqual(('MAGIC', 'PIXIE_DUST'), control_message.unknown_auth_methods)
def test_multiple_auth(self): """ Checks a response with multiple authentication methods. """ control_message = ControlMessage.from_str(MULTIPLE_AUTH, 'PROTOCOLINFO', normalize = True) self.assertEqual((AuthMethod.COOKIE, AuthMethod.PASSWORD), control_message.auth_methods) self.assertEqual('/home/atagar/.tor/control_auth_cookie', control_message.cookie_path)
def test_getconf(self): controller = Mock() controller.msg.return_value = ControlMessage.from_str('250-Log=notice stdout\r\n250 Address\r\n') interpreter = ControlInterpreter(controller) self.assertEqual('\x1b[34m250-Log=notice stdout\r\x1b[0m\n\x1b[34m250 Address\x1b[0m\n', interpreter.run_command('GETCONF log address')) controller.msg.assert_called_with('GETCONF log address')
def test_single_response(self): """ Parses a MAPADDRESS reply response with a single address mapping. """ control_message = ControlMessage.from_str('250 foo=bar\r\n', 'MAPADDRESS') self.assertEqual({'foo': 'bar'}, control_message.mapped)
def test_invalid_non_mapping_content(self): """ Parses a malformed GETINFO reply containing a line that isn't a key=value entry. """ control_message = ControlMessage.from_str(NON_KEY_VALUE_ENTRY, normalize = True) self.assertRaises(stem.ProtocolError, stem.response.convert, 'GETINFO', control_message)
def test_unicode_cookie(self): """ Checks an authentication cookie with a unicode path. """ control_message = ControlMessage.from_str(UNICODE_COOKIE_PATH, 'PROTOCOLINFO', normalize=True) self.assertEqual(EXPECTED_UNICODE_PATH, control_message.cookie_path)
def test_cookie_auth(self): """ Checks a response with cookie authentication and a path including escape characters. """ control_message = ControlMessage.from_str(COOKIE_AUTH, 'PROTOCOLINFO', normalize = True) self.assertEqual((AuthMethod.COOKIE, ), control_message.auth_methods) self.assertEqual('/tmp/my data\\"dir//control_auth_cookie', control_message.cookie_path)
def test_without_service_id(self): """ Checks a response that lack an initial service id. """ response = ControlMessage.from_str(WRONG_FIRST_KEY, normalize=True) self.assertRaisesRegexp(stem.ProtocolError, 'ADD_ONION response should start with', stem.response.convert, 'ADD_ONION', response)
def test_without_private_key(self): """ Checks a response without a private key. """ response = ControlMessage.from_str(WITHOUT_PRIVATE_KEY, 'ADD_ONION', normalize = True) self.assertEqual('gfzprpioee3hoppz', response.service_id) self.assertEqual(None, response.private_key) self.assertEqual(None, response.private_key_type)
def test_invalid_multiline_content(self): """ Parses a malformed GETINFO reply with a multi-line entry missing a newline between its key and value. This is a proper controller message, but malformed according to the GETINFO's spec. """ control_message = ControlMessage.from_str(MISSING_MULTILINE_NEWLINE, normalize = True) self.assertRaises(stem.ProtocolError, stem.response.convert, 'GETINFO', control_message)
def test_password_auth(self): """ Checks a response with password authentication. """ control_message = ControlMessage.from_str(PASSWORD_AUTH, 'PROTOCOLINFO', normalize=True) self.assertEqual((AuthMethod.PASSWORD, ), control_message.auth_methods)
def test_with_private_key(self): """ Checks a response when there's a private key. """ response = ControlMessage.from_str(WITH_PRIVATE_KEY, 'ADD_ONION', normalize = True) self.assertEqual('gfzprpioee3hoppz', response.service_id) self.assertTrue(response.private_key.startswith('MIICXgIBAAKB')) self.assertEqual('RSA1024', response.private_key_type) self.assertEqual({}, response.client_auth)
def test_single_response(self): """ Parses a GETINFO reply response for a single parameter. """ control_message = ControlMessage.from_str(SINGLE_RESPONSE, 'GETINFO', normalize=True) self.assertEqual({'version': b'0.2.3.11-alpha-dev'}, control_message.entries)
def test_latin_cookie_path(self): """ Parse a latin-1 path when our filesystem is configured for unicode. (:ticket:`57`) """ control_message = ControlMessage.from_str(LATIN_COOKIE_PATH, 'PROTOCOLINFO', normalize=True) self.assertEqual(EXPECTED_LATIN_PATH, control_message.cookie_path)
def test_with_client_auth(self): """ Checks a response when there's client credentials. """ response = ControlMessage.from_str(WITH_CLIENT_AUTH, 'ADD_ONION', normalize = True) self.assertEqual('oekn5sqrvcu4wote', response.service_id) self.assertEqual(None, response.private_key) self.assertEqual(None, response.private_key_type) self.assertEqual({'bob': 'lhwLVFt0Kd5/0Gy9DkKoyA', 'alice': 'T9UADxtrvqx2HnLKWp/fWQ'}, response.client_auth)
def test_invalid_non_mapping_content(self): """ Parses a malformed GETINFO reply containing a line that isn't a key=value entry. """ control_message = ControlMessage.from_str(NON_KEY_VALUE_ENTRY, normalize=True) self.assertRaises(stem.ProtocolError, stem.response.convert, 'GETINFO', control_message)
def test_no_key_type(self): """ Checks a response that's missing the private key type. """ response = ControlMessage.from_str(MISSING_KEY_TYPE, normalize=True) self.assertRaisesRegexp( stem.ProtocolError, 'ADD_ONION PrivateKey lines should be of the form', stem.response.convert, 'ADD_ONION', response)
def circ_bw(circ_id, read, sent, delivered_read, delivered_sent, overhead_read, overhead_sent): s = "650 CIRC_BW ID=" + str(circ_id) + " READ=" + str( int(read)) + " WRITTEN=" + str(int( sent)) + " TIME=2018-05-04T06:08:55.751726 DELIVERED_READ=" + str( int(delivered_read)) + " OVERHEAD_READ=" + str( int(overhead_read)) + " DELIVERED_WRITTEN=" + str( int(delivered_sent)) + " OVERHEAD_WRITTEN=" + str( int(overhead_sent)) + "\r\n" return ControlMessage.from_str(s, "EVENT")
def test_without_private_key(self): """ Checks a response without a private key. """ response = ControlMessage.from_str(WITHOUT_PRIVATE_KEY, 'ADD_ONION', normalize=True) self.assertEqual('gfzprpioee3hoppz', response.service_id) self.assertEqual(None, response.private_key) self.assertEqual(None, response.private_key_type)
def test_unrecognized_key_response(self): """ Parses a GETCONF reply that contains an error code with an unrecognized key. """ try: control_message = ControlMessage.from_str(UNRECOGNIZED_KEY_RESPONSE, normalize = True) stem.response.convert('GETCONF', control_message) self.fail('expected a stem.InvalidArguments to be raised') except stem.InvalidArguments as exc: self.assertEqual(exc.arguments, ['brickroad', 'submarine'])
def test_no_auth(self): """ Checks a response when there's no authentication. """ control_message = ControlMessage.from_str(NO_AUTH, 'PROTOCOLINFO', normalize = True) self.assertEqual(1, control_message.protocol_version) self.assertEqual(stem.version.Version('0.2.1.30'), control_message.tor_version) self.assertEqual((AuthMethod.NONE, ), control_message.auth_methods) self.assertEqual((), control_message.unknown_auth_methods) self.assertEqual(None, control_message.cookie_path)
def test_invalid_multiline_content(self): """ Parses a malformed GETINFO reply with a multi-line entry missing a newline between its key and value. This is a proper controller message, but malformed according to the GETINFO's spec. """ control_message = ControlMessage.from_str(MISSING_MULTILINE_NEWLINE, normalize=True) self.assertRaises(stem.ProtocolError, stem.response.convert, 'GETINFO', control_message)
def test_getconf(self): controller = Mock() controller.msg.return_value = ControlMessage.from_str( '250-Log=notice stdout\r\n250 Address\r\n') interpreter = ControlInterpreter(controller) self.assertEqual( '\x1b[34m250-Log=notice stdout\r\x1b[0m\n\x1b[34m250 Address\x1b[0m\n', interpreter.run_command('GETCONF log address')) controller.msg.assert_called_with('GETCONF log address')
def test_multivalue_response(self): """ Parses a GETCONF reply containing a single key with multiple parameters. """ expected = { 'ControlPort': ['9100'], 'ExitPolicy': ['accept 34.3.4.5', 'accept 3.4.53.3', 'accept 3.4.53.3', 'reject 23.245.54.3'] } control_message = ControlMessage.from_str(MULTIVALUE_RESPONSE, 'GETCONF', normalize = True) self.assertEqual(expected, control_message.entries)
def test_empty_response(self): """ Parses a GETCONF reply without options (just calling "GETCONF"). """ control_message = ControlMessage.from_str('250 OK\r\n', 'GETCONF') # now this should be a GetConfResponse (ControlMessage subclass) self.assertTrue(isinstance(control_message, stem.response.ControlMessage)) self.assertTrue(isinstance(control_message, stem.response.getconf.GetConfResponse)) self.assertEqual({}, control_message.entries)
def test_minimum_response(self): """ Checks a PROTOCOLINFO response that only contains the minimum amount of information to be a valid response. """ control_message = ControlMessage.from_str(MINIMUM_RESPONSE, 'PROTOCOLINFO', normalize = True) self.assertEqual(5, control_message.protocol_version) self.assertEqual(None, control_message.tor_version) self.assertEqual((), control_message.auth_methods) self.assertEqual((), control_message.unknown_auth_methods) self.assertEqual(None, control_message.cookie_path)
def test_getinfo(self): controller = Mock() controller.msg.return_value = ControlMessage.from_str('250-version=0.2.5.1-alpha-dev (git-245ecfff36c0cecc)\r\n250 OK\r\n') interpreter = ControlInterpreter(controller) self.assertEqual('\x1b[34m250-version=0.2.5.1-alpha-dev (git-245ecfff36c0cecc)\r\x1b[0m\n\x1b[34m250 OK\x1b[0m\n', interpreter.run_command('GETINFO version')) self.assertEqual('\x1b[34m250-version=0.2.5.1-alpha-dev (git-245ecfff36c0cecc)\r\x1b[0m\n\x1b[34m250 OK\x1b[0m\n', interpreter.run_command('GETINFO version')) controller.msg.assert_called_with('GETINFO version') controller.msg.side_effect = stem.ControllerError('kaboom!') self.assertEqual('\x1b[1;31mkaboom!\x1b[0m\n', interpreter.run_command('getinfo process/user'))
def test_multiline_response(self): """ Parses a GETINFO reply for multiple parameters including a multi-line value. """ expected = { 'version': b'0.2.3.11-alpha-dev (git-ef0bc7f8f26a917c)', 'config-text': b'\n'.join(stem.util.str_tools._to_bytes(MULTILINE_RESPONSE).splitlines()[2:8]), } control_message = ControlMessage.from_str(MULTILINE_RESPONSE, 'GETINFO', normalize = True) self.assertEqual(expected, control_message.entries)
def test_valid_response(self): """ Parses valid AUTHCHALLENGE responses. """ control_message = ControlMessage.from_str(VALID_RESPONSE, 'AUTHCHALLENGE', normalize = True) # now this should be a AuthChallengeResponse (ControlMessage subclass) self.assertTrue(isinstance(control_message, stem.response.ControlMessage)) self.assertTrue(isinstance(control_message, stem.response.authchallenge.AuthChallengeResponse)) self.assertEqual(VALID_HASH, control_message.server_hash) self.assertEqual(VALID_NONCE, control_message.server_nonce)