def test_convert(self): """ Exercises functionality of the convert method both when it works and there's an error. """ # working case control_message = mocking.get_message(NO_AUTH) stem.response.convert("PROTOCOLINFO", control_message) # 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.assertEquals(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 bw_event_control_message = mocking.get_message("650 BW 32326 2856") self.assertRaises(stem.ProtocolError, stem.response.convert, "PROTOCOLINFO", bw_event_control_message)
def test_relative_cookie(self): """ Checks an authentication cookie with a relative path where expansion both succeeds and fails. """ # we need to mock both pid and cwd lookups since the general cookie # expanion works by... # - resolving the pid of the "tor" process # - using that to get tor's cwd def call_function(command, default): if command == stem.util.system.GET_PID_BY_NAME_PGREP % "tor": return ["10"] elif command == stem.util.system.GET_CWD_PWDX % 10: return ["10: /tmp/foo"] with patch('stem.util.system.call') as call_mock: call_mock.side_effect = call_function control_message = mocking.get_message(RELATIVE_COOKIE_PATH) stem.response.convert("PROTOCOLINFO", control_message) stem.connection._expand_cookie_path(control_message, stem.util.system.get_pid_by_name, "tor") self.assertEquals(os.path.join("/tmp/foo", "tor-browser_en-US", "Data", "control_auth_cookie"), control_message.cookie_path) # exercise cookie expansion where both calls fail (should work, just # leaving the path unexpanded) with patch('stem.util.system.call', Mock(return_value = None)): control_message = mocking.get_message(RELATIVE_COOKIE_PATH) stem.response.convert("PROTOCOLINFO", control_message) self.assertEquals("./tor-browser_en-US/Data/control_auth_cookie", control_message.cookie_path)
def test_convert(self): """ Exercises functionality of the convert method both when it works and there's an error. """ # working case control_message = mocking.get_message(NO_AUTH) stem.response.convert("PROTOCOLINFO", control_message) # 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.assertEquals(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 bw_event_control_message = mocking.get_message("650 BW 32326 2856") self.assertRaises(stem.ProtocolError, stem.response.convert, "PROTOCOLINFO", bw_event_control_message)
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 = mocking.get_message(INVALID_EMPTY_RESPONSE) self.assertRaises(stem.socket.ProtocolError, stem.response.convert, "MAPADDRESS", control_message) control_message = mocking.get_message(INVALID_RESPONSE) self.assertRaises(stem.socket.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 = mocking.get_message(UNRECOGNIZED_KEYS_RESPONSE) self.assertRaises(stem.socket.InvalidRequest, stem.response.convert, "MAPADDRESS", control_message) expected = { "23": "324" } control_message = mocking.get_message(PARTIAL_FAILURE_RESPONSE) stem.response.convert("MAPADDRESS", control_message) self.assertEqual(expected, control_message.entries)
def test_invalid_requests(self): """ Parses a MAPADDRESS replies that contain an error code due to hostname syntax errors. """ control_message = mocking.get_message(UNRECOGNIZED_KEYS_RESPONSE) self.assertRaises(stem.InvalidRequest, stem.response.convert, 'MAPADDRESS', control_message) expected = {'23': '324'} control_message = mocking.get_message(PARTIAL_FAILURE_RESPONSE) stem.response.convert('MAPADDRESS', control_message) self.assertEqual(expected, control_message.entries)
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 = mocking.get_message(INVALID_EMPTY_RESPONSE) self.assertRaises(stem.ProtocolError, stem.response.convert, 'MAPADDRESS', control_message) control_message = mocking.get_message(INVALID_RESPONSE) self.assertRaises(stem.ProtocolError, stem.response.convert, 'MAPADDRESS', control_message)
def test_single_line_response(self): message = mocking.get_message('552 NOTOK') stem.response.convert('SINGLELINE', message) self.assertEqual(False, message.is_ok()) message = mocking.get_message('250 KK') stem.response.convert('SINGLELINE', message) self.assertEqual(True, message.is_ok()) message = mocking.get_message('250 OK') stem.response.convert('SINGLELINE', message) self.assertEqual(True, message.is_ok(True)) message = mocking.get_message('250 HMM') stem.response.convert('SINGLELINE', message) self.assertEqual(False, message.is_ok(True))
def test_single_line_response(self): message = mocking.get_message("552 NOTOK") stem.response.convert("SINGLELINE", message) self.assertEqual(False, message.is_ok()) message = mocking.get_message("250 KK") stem.response.convert("SINGLELINE", message) self.assertEqual(True, message.is_ok()) message = mocking.get_message("250 OK") stem.response.convert("SINGLELINE", message) self.assertEqual(True, message.is_ok(True)) message = mocking.get_message("250 HMM") stem.response.convert("SINGLELINE", message) self.assertEqual(False, message.is_ok(True))
def test_setevents(self): controller = Mock() controller.msg.return_value = mocking.get_message('250 OK') interpreter = ControlInterpreter(controller) self.assertEqual('\x1b[34m250 OK\x1b[0m\n', interpreter.run_command('SETEVENTS BW'))
def test_setevents(self): controller = Mock() controller.msg.return_value = mocking.get_message('250 OK') interpreter = ControlInterpretor(controller) self.assertEqual('\x1b[34m250 OK\x1b[0m\n', interpreter.run_command('SETEVENTS BW'))
def test_single_response(self): """ Parses a MAPADDRESS reply response with a single address mapping. """ control_message = mocking.get_message(SINGLE_RESPONSE) stem.response.convert("MAPADDRESS", control_message) self.assertEqual({"foo": "bar"}, control_message.entries)
def test_single_response(self): """ Parses a GETINFO reply response for a single parameter. """ control_message = mocking.get_message(SINGLE_RESPONSE) stem.response.convert('GETINFO', control_message) self.assertEqual({'version': b'0.2.3.11-alpha-dev'}, control_message.entries)
def test_invalid_non_mapping_content(self): """ Parses a malformed GETINFO reply containing a line that isn't a key=value entry. """ control_message = mocking.get_message(NON_KEY_VALUE_ENTRY) self.assertRaises(stem.ProtocolError, stem.response.convert, 'GETINFO', control_message)
def test_single_response(self): """ Parses a GETINFO reply response for a single parameter. """ control_message = mocking.get_message(SINGLE_RESPONSE) stem.response.convert("GETINFO", control_message) self.assertEqual({"version": "0.2.3.11-alpha-dev"}, control_message.entries)
def test_single_response(self): """ Parses a GETCONF reply response for a single parameter. """ control_message = mocking.get_message(SINGLE_RESPONSE) stem.response.convert("GETCONF", control_message) self.assertEqual({"DataDirectory": ["/home/neena/.tor"]}, control_message.entries)
def test_unicode_cookie(self): """ Checks an authentication cookie with a unicode path. """ control_message = mocking.get_message(UNICODE_COOKIE_PATH) stem.response.convert('PROTOCOLINFO', control_message) self.assertEqual(EXPECTED_UNICODE_PATH, control_message.cookie_path)
def test_password_auth(self): """ Checks a response with password authentication. """ control_message = mocking.get_message(PASSWORD_AUTH) stem.response.convert('PROTOCOLINFO', control_message) self.assertEqual((AuthMethod.PASSWORD, ), control_message.auth_methods)
def test_single_response(self): """ Parses a MAPADDRESS reply response with a single address mapping. """ control_message = mocking.get_message(SINGLE_RESPONSE) stem.response.convert('MAPADDRESS', control_message) self.assertEqual({'foo': 'bar'}, control_message.entries)
def test_invalid_non_mapping_content(self): """ Parses a malformed GETINFO reply containing a line that isn't a key=value entry. """ control_message = mocking.get_message(NON_KEY_VALUE_ENTRY) self.assertRaises(stem.socket.ProtocolError, stem.response.convert, "GETINFO", control_message)
def test_password_auth(self): """ Checks a response with password authentication. """ control_message = mocking.get_message(PASSWORD_AUTH) stem.response.convert("PROTOCOLINFO", control_message) self.assertEquals((AuthMethod.PASSWORD, ), control_message.auth_methods)
def test_relative_cookie(self): """ Checks an authentication cookie with a relative path where expansion both succeeds and fails. """ # TODO: move into stem.connection unit tests? # we need to mock both pid and cwd lookups since the general cookie # expanion works by... # - resolving the pid of the "tor" process # - using that to get tor's cwd def call_mocking(command): if command == stem.util.system.GET_PID_BY_NAME_PGREP % "tor": return ["10"] elif command == stem.util.system.GET_CWD_PWDX % 10: return ["10: /tmp/foo"] mocking.mock(stem.util.proc.is_available, mocking.return_false()) mocking.mock(stem.util.system.is_available, mocking.return_true()) mocking.mock(stem.util.system.call, call_mocking) control_message = mocking.get_message(RELATIVE_COOKIE_PATH) stem.response.convert("PROTOCOLINFO", control_message) stem.connection._expand_cookie_path(control_message, stem.util.system.get_pid_by_name, "tor") self.assertEquals( os.path.join("/tmp/foo", "tor-browser_en-US", "Data", "control_auth_cookie"), control_message.cookie_path) # exercise cookie expansion where both calls fail (should work, just # leaving the path unexpanded) mocking.mock(stem.util.system.call, mocking.return_none()) control_message = mocking.get_message(RELATIVE_COOKIE_PATH) stem.response.convert("PROTOCOLINFO", control_message) self.assertEquals("./tor-browser_en-US/Data/control_auth_cookie", control_message.cookie_path) # reset system call mocking mocking.revert_mocking()
def test_unknown_auth(self): """ Checks a response with an unrecognized authtentication method. """ control_message = mocking.get_message(UNKNOWN_AUTH) stem.response.convert("PROTOCOLINFO", control_message) self.assertEquals((AuthMethod.UNKNOWN, AuthMethod.PASSWORD), control_message.auth_methods) self.assertEquals(("MAGIC", "PIXIE_DUST"), control_message.unknown_auth_methods)
def test_multiple_auth(self): """ Checks a response with multiple authentication methods. """ control_message = mocking.get_message(MULTIPLE_AUTH) stem.response.convert("PROTOCOLINFO", control_message) self.assertEquals((AuthMethod.COOKIE, AuthMethod.PASSWORD), control_message.auth_methods) self.assertEquals("/home/atagar/.tor/control_auth_cookie", control_message.cookie_path)
def test_invalid_content(self): """ Parses a malformed GETCONF reply that contains an invalid response code. This is a proper controller message, but malformed according to the GETCONF's spec. """ control_message = mocking.get_message(INVALID_RESPONSE) self.assertRaises(stem.ProtocolError, stem.response.convert, "GETCONF", control_message)
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 = mocking.get_message(MISSING_MULTILINE_NEWLINE) self.assertRaises(stem.ProtocolError, stem.response.convert, 'GETINFO', control_message)
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 = mocking.get_message(MISSING_MULTILINE_NEWLINE) self.assertRaises(stem.socket.ProtocolError, stem.response.convert, "GETINFO", control_message)
def test_invalid_content(self): """ Parses a malformed GETCONF reply that contains an invalid response code. This is a proper controller message, but malformed according to the GETCONF's spec. """ control_message = mocking.get_message(INVALID_RESPONSE) self.assertRaises(stem.socket.ProtocolError, stem.response.convert, "GETCONF", control_message)
def test_getconf(self): response = '250-Log=notice stdout\r\n250 Address' controller = Mock() controller.msg.return_value = mocking.get_message(response) 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_cookie_auth(self): """ Checks a response with cookie authentication and a path including escape characters. """ control_message = mocking.get_message(COOKIE_AUTH) stem.response.convert("PROTOCOLINFO", control_message) self.assertEquals((AuthMethod.COOKIE, ), control_message.auth_methods) self.assertEquals("/tmp/my data\\\"dir//control_auth_cookie", control_message.cookie_path)
def test_getconf(self): response = '250-Log=notice stdout\r\n250 Address' controller = Mock() controller.msg.return_value = mocking.get_message(response) interpreter = ControlInterpretor(controller) self.assertEqual('\x1b[34m%s\x1b[0m\n' % response, interpreter.run_command('GETCONF log address')) controller.msg.assert_called_with('GETCONF log address')
def test_with_private_key(self): """ Checks a response when there's a private key. """ response = mocking.get_message(WITH_PRIVATE_KEY) stem.response.convert('ADD_ONION', response) self.assertEqual('gfzprpioee3hoppz', response.service_id) self.assertTrue(response.private_key.startswith('MIICXgIBAAKB')) self.assertEqual('RSA1024', response.private_key_type)
def test_without_private_key(self): """ Checks a response without a private key. """ response = mocking.get_message(WITHOUT_PRIVATE_KEY) stem.response.convert('ADD_ONION', response) self.assertEqual('gfzprpioee3hoppz', response.service_id) self.assertEqual(None, response.private_key) self.assertEqual(None, response.private_key_type)
def test_no_key_type(self): """ Checks a response that's missing the private key type. """ try: response = mocking.get_message(MISSING_KEY_TYPE) stem.response.convert('ADD_ONION', response) self.fail("we should've raised a ProtocolError") except stem.ProtocolError as exc: self.assertTrue(str(exc).startswith('ADD_ONION PrivateKey lines should be of the form'))
def test_without_service_id(self): """ Checks a response that lack an initial service id. """ try: response = mocking.get_message(WRONG_FIRST_KEY) stem.response.convert('ADD_ONION', response) self.fail("we should've raised a ProtocolError") except stem.ProtocolError as exc: self.assertTrue(str(exc).startswith('ADD_ONION response should start with'))
def test_unrecognized_key_response(self): """ Parses a GETCONF reply that contains an error code with an unrecognized key. """ control_message = mocking.get_message(UNRECOGNIZED_KEY_ENTRY) self.assertRaises(stem.InvalidArguments, stem.response.convert, "GETINFO", control_message) try: stem.response.convert("GETINFO", control_message) except stem.InvalidArguments, exc: self.assertEqual(exc.arguments, ["blackhole"])
def test_unrecognized_key_response(self): """ Parses a GETCONF reply that contains an error code with an unrecognized key. """ control_message = mocking.get_message(UNRECOGNIZED_KEY_RESPONSE) self.assertRaises(stem.socket.InvalidArguments, stem.response.convert, "GETCONF", control_message) try: stem.response.convert("GETCONF", control_message) except stem.socket.InvalidArguments, exc: self.assertEqual(exc.arguments, ["brickroad", "submarine"])
def test_unrecognized_key_response(self): """ Parses a GETCONF reply that contains an error code with an unrecognized key. """ control_message = mocking.get_message(UNRECOGNIZED_KEY_RESPONSE) self.assertRaises(stem.InvalidArguments, stem.response.convert, "GETCONF", control_message) try: stem.response.convert("GETCONF", control_message) except stem.InvalidArguments, exc: self.assertEqual(exc.arguments, ["brickroad", "submarine"])
def test_unrecognized_key_response(self): """ Parses a GETCONF reply that contains an error code with an unrecognized key. """ control_message = mocking.get_message(UNRECOGNIZED_KEY_ENTRY) self.assertRaises(stem.InvalidArguments, stem.response.convert, 'GETINFO', control_message) try: stem.response.convert('GETINFO', control_message) except stem.InvalidArguments as exc: self.assertEqual(exc.arguments, ['blackhole'])
def test_relative_cookie(self): """ Checks an authentication cookie with a relative path where expansion both succeeds and fails. """ # TODO: move into stem.connection unit tests? # we need to mock both pid and cwd lookups since the general cookie # expanion works by... # - resolving the pid of the "tor" process # - using that to get tor's cwd def call_mocking(command): if command == stem.util.system.GET_PID_BY_NAME_PGREP % "tor": return ["10"] elif command == stem.util.system.GET_CWD_PWDX % 10: return ["10: /tmp/foo"] mocking.mock(stem.util.proc.is_available, mocking.return_false()) mocking.mock(stem.util.system.is_available, mocking.return_true()) mocking.mock(stem.util.system.call, call_mocking) control_message = mocking.get_message(RELATIVE_COOKIE_PATH) stem.response.convert("PROTOCOLINFO", control_message) stem.connection._expand_cookie_path(control_message, stem.util.system.get_pid_by_name, "tor") self.assertEquals(os.path.join("/tmp/foo", "tor-browser_en-US", "Data", "control_auth_cookie"), control_message.cookie_path) # exercise cookie expansion where both calls fail (should work, just # leaving the path unexpanded) mocking.mock(stem.util.system.call, mocking.return_none()) control_message = mocking.get_message(RELATIVE_COOKIE_PATH) stem.response.convert("PROTOCOLINFO", control_message) self.assertEquals("./tor-browser_en-US/Data/control_auth_cookie", control_message.cookie_path) # reset system call mocking mocking.revert_mocking()
def test_relative_cookie(self): """ Checks an authentication cookie with a relative path where expansion both succeeds and fails. """ # we need to mock both pid and cwd lookups since the general cookie # expanion works by... # - resolving the pid of the "tor" process # - using that to get tor's cwd def call_function(command, default): if command == stem.util.system.GET_PID_BY_NAME_PGREP % 'tor': return ['10'] elif command == stem.util.system.GET_CWD_PWDX % 10: return ['10: /tmp/foo'] with patch('stem.util.system.call') as call_mock: call_mock.side_effect = call_function control_message = mocking.get_message(RELATIVE_COOKIE_PATH) stem.response.convert('PROTOCOLINFO', control_message) stem.connection._expand_cookie_path(control_message, stem.util.system.pid_by_name, 'tor') self.assertEqual( os.path.join('/tmp/foo', 'tor-browser_en-US', 'Data', 'control_auth_cookie'), control_message.cookie_path) # exercise cookie expansion where both calls fail (should work, just # leaving the path unexpanded) with patch('stem.util.system.call', Mock(return_value=None)): control_message = mocking.get_message(RELATIVE_COOKIE_PATH) stem.response.convert('PROTOCOLINFO', control_message) self.assertEqual('./tor-browser_en-US/Data/control_auth_cookie', control_message.cookie_path)
def test_no_auth(self): """ Checks a response when there's no authentication. """ control_message = mocking.get_message(NO_AUTH) stem.response.convert("PROTOCOLINFO", control_message) self.assertEquals(1, control_message.protocol_version) self.assertEquals(stem.version.Version("0.2.1.30"), control_message.tor_version) self.assertEquals((AuthMethod.NONE, ), control_message.auth_methods) self.assertEquals((), control_message.unknown_auth_methods) self.assertEquals(None, control_message.cookie_path)
def test_empty_response(self): """ Parses a GETCONF reply without options (just calling "GETCONF"). """ control_message = mocking.get_message(EMPTY_RESPONSE) stem.response.convert("GETCONF", control_message) # 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_getinfo(self): response = '250-version=0.2.5.1-alpha-dev (git-245ecfff36c0cecc)\r\n250 OK' controller = Mock() controller.msg.return_value = mocking.get_message(response) interpreter = ControlInterpretor(controller) self.assertEqual('\x1b[34m%s\x1b[0m\n' % response, 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_empty_response(self): """ Parses a GETINFO reply without options (just calling "GETINFO"). """ control_message = mocking.get_message(EMPTY_RESPONSE) stem.response.convert("GETINFO", control_message) # now this should be a GetInfoResponse (ControlMessage subclass) self.assertTrue(isinstance(control_message, stem.response.ControlMessage)) self.assertTrue(isinstance(control_message, stem.response.getinfo.GetInfoResponse)) self.assertEqual({}, control_message.entries)
def test_multivalue_response(self): """ Parses a GETCONF reply containing a single key with multiple parameters. """ control_message = mocking.get_message(MULTIVALUE_RESPONSE) stem.response.convert('GETCONF', control_message) expected = { 'ControlPort': ['9100'], 'ExitPolicy': ['accept 34.3.4.5', 'accept 3.4.53.3', 'accept 3.4.53.3', 'reject 23.245.54.3'] } self.assertEqual(expected, control_message.entries)
def test_multivalue_response(self): """ Parses a GETCONF reply containing a single key with multiple parameters. """ control_message = mocking.get_message(MULTIVALUE_RESPONSE) stem.response.convert("GETCONF", control_message) expected = { "ControlPort": ["9100"], "ExitPolicy": ["accept 34.3.4.5", "accept 3.4.53.3", "accept 3.4.53.3", "reject 23.245.54.3"] } self.assertEqual(expected, 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 = mocking.get_message(MINIMUM_RESPONSE) stem.response.convert("PROTOCOLINFO", control_message) self.assertEquals(5, control_message.protocol_version) self.assertEquals(None, control_message.tor_version) self.assertEquals((), control_message.auth_methods) self.assertEquals((), control_message.unknown_auth_methods) self.assertEquals(None, control_message.cookie_path)
def test_valid_response(self): """ Parses valid AUTHCHALLENGE responses. """ control_message = mocking.get_message(VALID_RESPONSE) stem.response.convert('AUTHCHALLENGE', control_message) # 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)
def test_multiline_response(self): """ Parses a GETINFO reply for multiple parameters including a multi-line value. """ control_message = mocking.get_message(MULTILINE_RESPONSE) stem.response.convert("GETINFO", control_message) expected = { "version": "0.2.3.11-alpha-dev (git-ef0bc7f8f26a917c)", "config-text": "\n".join(MULTILINE_RESPONSE.splitlines()[2:8]), } self.assertEqual(expected, control_message.entries)