def test_no_state_ini_file_or_state_dir(self, mock_os, mock_file_exists, mock_path_exists, mock_psws, mock_management_util, mock_tabpy_state, mock_parse_arguments): TabPyApp(None) self.assertEqual(len(mock_os.makedirs.mock_calls), 1)
def test_http(self): self.fp.write("[TabPy]\n" "TABPY_TRANSFER_PROTOCOL = http") self.fp.close() app = TabPyApp(self.fp.name) self.assertEqual(app.settings['transfer_protocol'], 'http')
def test_config_file_present(self, mock_os, mock_path_exists, mock_psws, mock_management_util, mock_tabpy_state, mock_parse_arguments): config_file = NamedTemporaryFile(delete=False) config_file.write("[TabPy]\n" "TABPY_QUERY_OBJECT_PATH = foo\n" "TABPY_STATE_PATH = bar\n".encode()) config_file.close() mock_parse_arguments.return_value = Namespace(config=config_file.name) mock_os.getenv.side_effect = [1234] mock_os.path.realpath.return_value = 'bar' app = TabPyApp(config_file.name) getenv_calls = [call('TABPY_PORT', 9004)] mock_os.getenv.assert_has_calls(getenv_calls, any_order=True) self.assertEqual(app.settings['port'], 1234) self.assertEqual(app.settings['server_version'], open('VERSION').read().strip()) self.assertEqual(app.settings['upload_dir'], 'foo') self.assertEqual(app.settings['state_file_path'], 'bar') self.assertEqual(app.settings['transfer_protocol'], 'http') self.assertTrue('certificate_file' not in app.settings) self.assertTrue('key_file' not in app.settings) os.remove(config_file.name)
class TestServiceInfoHandlerDefault(AsyncHTTPTestCase): @classmethod def setUpClass(cls): cls.patcher = patch( 'tabpy_server.app.app.TabPyApp._parse_cli_arguments', return_value=Namespace( config=None)) cls.patcher.start() @classmethod def tearDownClass(cls): cls.patcher.stop() def get_app(self): self.app = TabPyApp() return self.app._create_tornado_web_app() def test_given_vanilla_tabpy_server_expect_correct_info_response(self): response = self.fetch('/info') self.assertEqual(response.code, 200) actual_response = json.loads(response.body) expected_response = _create_expected_info_response( self.app.settings, self.app.tabpy_state) self.assertDictEqual(actual_response, expected_response)
class TestServiceInfoHandlerWithoutAuth(AsyncHTTPTestCase): @classmethod def setUpClass(cls): prefix = '__TestServiceInfoHandlerWithoutAuth_' # create state.ini dir and file cls.state_dir = tempfile.mkdtemp(prefix=prefix) with open(os.path.join(cls.state_dir, 'state.ini'), 'w+')\ as cls.state_file: cls.state_file.write('[Service Info]\n' 'Name = TabPy Serve\n' 'Description = \n' 'Creation Time = 0\n' 'Access-Control-Allow-Origin = \n' 'Access-Control-Allow-Headers = \n' 'Access-Control-Allow-Methods = \n' '\n' '[Query Objects Service Versions]\n' '\n' '[Query Objects Docstrings]\n' '\n' '[Meta]\n' 'Revision Number = 1\n') cls.state_file.close() # create config file cls.config_file = tempfile.NamedTemporaryFile(prefix=prefix, suffix='.conf', delete=False, mode='w+') cls.config_file.write('[TabPy]\n' 'TABPY_STATE_PATH = {}'.format(cls.state_dir)) cls.config_file.close() @classmethod def tearDownClass(cls): os.remove(cls.state_file.name) os.remove(cls.config_file.name) os.rmdir(cls.state_dir) def get_app(self): self.app = TabPyApp(self.config_file.name) return self.app._create_tornado_web_app() def test_tabpy_server_with_no_auth_expect_correct_info_response(self): response = self.fetch('/info') self.assertEqual(response.code, 200) actual_response = json.loads(response.body) expected_response = _create_expected_info_response( self.app.settings, self.app.tabpy_state) self.assertDictEqual(actual_response, expected_response) self.assertTrue('versions' in actual_response) versions = actual_response['versions'] self.assertTrue('v1' in versions) v1 = versions['v1'] self.assertTrue('features' in v1) features = v1['features'] self.assertDictEqual({}, features)
def test_given_no_pwd_file_expect_empty_credentials_list(self): self._set_file(self.config_file.name, "[TabPy]\n" "TABPY_TRANSFER_PROTOCOL = http") app = TabPyApp(self.config_file.name) self.assertDictEqual( app.credentials, {}, 'Expected no credentials with no password file provided')
def test_given_missing_pwd_file_expect_app_fails(self): self._set_file(self.config_file.name, "[TabPy]\n" "TABPY_PWD_FILE = foo") with self.assertRaises(RuntimeError) as cm: TabPyApp(self.config_file.name) ex = cm.exception self.assertEqual( 'Failed to read password file {}'.format(self.pwd_file.name), ex.args[0])
def test_custom_evaluate_timeout_invalid(self, mock_state, mock_get_state_from_file, mock_path_exists): self.assertTrue(self.config_file is not None) config_file = self.config_file config_file.write('[TabPy]\n' 'TABPY_EVALUATE_TIMEOUT = "im not a float"'.encode()) config_file.close() app = TabPyApp(self.config_file.name) self.assertEqual(app.settings['evaluate_timeout'], 30.0)
def test_https_success(self, mock_isfile, mock_validate_cert): self.fp.write("[TabPy]\n" "TABPY_TRANSFER_PROTOCOL = HtTpS\n" "TABPY_CERTIFICATE_FILE = foo\n" "TABPY_KEY_FILE = bar") self.fp.close() app = TabPyApp(self.fp.name) self.assertEqual(app.settings['transfer_protocol'], 'https') self.assertEqual(app.settings['certificate_file'], 'foo') self.assertEqual(app.settings['key_file'], 'bar')
def test_given_empty_pwd_file_expect_app_fails(self): self._set_file(self.config_file.name, '[TabPy]\n' f'TABPY_PWD_FILE = {self.pwd_file.name}') self._set_file(self.pwd_file.name, "# just a comment") with self.assertRaises(RuntimeError) as cm: TabPyApp(self.config_file.name) ex = cm.exception self.assertEqual( f'Failed to read password file {self.pwd_file.name}', ex.args[0])
def test_given_different_cases_in_pwd_file_expect_app_fails(self): self._set_file(self.config_file.name, "[TabPy]\n" "TABPY_PWD_FILE = {}".format(self.pwd_file.name)) self._set_file(self.pwd_file.name, "# passwords\n" "user1 pwd1\n" "user_2 pwd#2" "UseR1 pwd@3") with self.assertRaises(RuntimeError) as cm: TabPyApp(self.config_file.name) ex = cm.exception self.assertEqual('Failed to read password file {}'.format( self.pwd_file.name), ex.args[0])
def test_given_one_password_in_pwd_file_expect_one_credentials_entry(self): self._set_file( self.config_file.name, "[TabPy]\n" "TABPY_PWD_FILE = {}".format(self.pwd_file.name)) login = '******' pwd = 'someting@something_else' self._set_file(self.pwd_file.name, "# passwords\n" "\n" "{} {}".format(login, pwd)) app = TabPyApp(self.config_file.name) self.assertEqual(len(app.credentials), 1) self.assertIn(login, app.credentials) self.assertEqual(app.credentials[login], pwd)
def test_given_one_line_with_too_many_params_expect_app_fails(self): self._set_file(self.config_file.name, "[TabPy]\n" f'TABPY_PWD_FILE = {self.pwd_file.name}') self._set_file(self.pwd_file.name, "# passwords\n" "user1 pwd1\n" "user_2 pwd#2" "user1 pwd@3") with self.assertRaises(RuntimeError) as cm: TabPyApp(self.config_file.name) ex = cm.exception self.assertEqual( f'Failed to read password file {self.pwd_file.name}', ex.args[0])
def test_given_username_but_no_password_expect_parsing_fails(self): self._set_file( self.config_file.name, "[TabPy]\n" "TABPY_PWD_FILE = {}".format(self.pwd_file.name)) login = '******' pwd = '' self._set_file(self.pwd_file.name, "# passwords\n" "\n" "{} {}".format(login, pwd)) with self.assertRaises(RuntimeError) as cm: TabPyApp(self.config_file.name) ex = cm.exception self.assertEqual( 'Failed to read password file {}'.format(self.pwd_file.name), ex.args[0])
def test_given_multiple_credentials_expect_all_parsed(self): self._set_file( self.config_file.name, "[TabPy]\n" "TABPY_PWD_FILE = {}".format(self.pwd_file.name)) creds = {'user_1': 'pwd_1', 'user@2': 'pwd@2', 'user#3': 'pwd#3'} pwd_file_context = "" for login in creds: pwd_file_context += '{} {}\n'.format(login, creds[login]) self._set_file(self.pwd_file.name, pwd_file_context) app = TabPyApp(self.config_file.name) self.assertCountEqual(creds, app.credentials) for login in creds: self.assertIn(login, app.credentials) self.assertEqual(creds[login], app.credentials[login])
def test_no_config_file(self, mock_os, mock_file_exists, mock_path_exists, mock_psws, mock_management_util, mock_tabpy_state, mock_parse_arguments): TabPyApp(None) getenv_calls = [call('TABPY_PORT', 9004), call('TABPY_QUERY_OBJECT_PATH', '/tmp/query_objects'), call('TABPY_STATE_PATH', './tabpy-server/tabpy_server')] mock_os.getenv.assert_has_calls(getenv_calls, any_order=True) self.assertEqual(len(mock_file_exists.mock_calls), 2) self.assertEqual(len(mock_psws.mock_calls), 1) self.assertEqual(len(mock_tabpy_state.mock_calls), 1) self.assertEqual(len(mock_path_exists.mock_calls), 1) self.assertTrue(len(mock_management_util.mock_calls) > 0) mock_os.makedirs.assert_not_called()
def test_given_duplicate_usernames_expect_parsing_fails(self): self._set_file(self.config_file.name, "[TabPy]\n" f'TABPY_PWD_FILE = {self.pwd_file.name}') login = '******' pwd = 'hashedpw' self._set_file(self.pwd_file.name, "# passwords\n" "\n" f'{login} {pwd}\n{login} {pwd}') with self.assertRaises(RuntimeError) as cm: TabPyApp(self.config_file.name) ex = cm.exception self.assertEqual( f'Failed to read password file {self.pwd_file.name}', ex.args[0])
def get_app(self): self.app = TabPyApp(self.config_file.name) return self.app._create_tornado_web_app()
class TestEvaluationPlainHandlerWithAuth(AsyncHTTPTestCase): @classmethod def setUpClass(cls): cls.patcher = patch( 'tabpy_server.app.app.TabPyApp._parse_cli_arguments', return_value=Namespace(config=None)) cls.patcher.start() prefix = '__TestEvaluationPlainHandlerWithAuth_' # create password file cls.pwd_file = tempfile.NamedTemporaryFile(mode='w+t', prefix=prefix, suffix='.txt', delete=False) username = '******' password = '******' cls.pwd_file.write(f'{username} {hash_password(username, password)}\n') cls.pwd_file.close() # create state.ini dir and file cls.state_dir = tempfile.mkdtemp(prefix=prefix) cls.state_file = open(os.path.join(cls.state_dir, 'state.ini'), 'w+') cls.state_file.write('[Service Info]\n' 'Name = TabPy Serve\n' 'Description = \n' 'Creation Time = 0\n' 'Access-Control-Allow-Origin = \n' 'Access-Control-Allow-Headers = \n' 'Access-Control-Allow-Methods = \n' '\n' '[Query Objects Service Versions]\n' '\n' '[Query Objects Docstrings]\n' '\n' '[Meta]\n' 'Revision Number = 1\n') cls.state_file.close() # create config file cls.config_file = tempfile.NamedTemporaryFile(mode='w+t', prefix=prefix, suffix='.conf', delete=False) cls.config_file.write('[TabPy]\n' f'TABPY_PWD_FILE = {cls.pwd_file.name}\n' f'TABPY_STATE_PATH = {cls.state_dir}') cls.config_file.close() cls.script =\ '{"data":{"_arg1":[2,3],"_arg2":[3,-1]},'\ '"script":"res=[]\\nfor i in range(len(_arg1)):\\n '\ 'res.append(_arg1[i] * _arg2[i])\\nreturn res"}' cls.script_not_present =\ '{"data":{"_arg1":[2,3],"_arg2":[3,-1]},'\ '"":"res=[]\\nfor i in range(len(_arg1)):\\n '\ 'res.append(_arg1[i] * _arg2[i])\\nreturn res"}' cls.args_not_present =\ '{"script":"res=[]\\nfor i in range(len(_arg1)):\\n '\ 'res.append(_arg1[i] * _arg2[i])\\nreturn res"}' cls.args_not_sequential =\ '{"data":{"_arg1":[2,3],"_arg3":[3,-1]},'\ '"script":"res=[]\\nfor i in range(len(_arg1)):\\n '\ 'res.append(_arg1[i] * _arg3[i])\\nreturn res"}' @classmethod def tearDownClass(cls): cls.patcher.stop() os.remove(cls.pwd_file.name) os.remove(cls.state_file.name) os.remove(cls.config_file.name) os.rmdir(cls.state_dir) def get_app(self): self.app = TabPyApp(self.config_file.name) return self.app._create_tornado_web_app() def test_no_creds_required_auth_fails(self): response = self.fetch('/evaluate', method='POST', body=self.script) self.assertEqual(401, response.code) def test_invalid_creds_fails(self): response = self.fetch( '/evaluate', method='POST', body=self.script, headers={ 'Authorization': 'Basic {}'.format( base64.b64encode( 'user:wrong_password'.encode('utf-8')).decode('utf-8')) }) self.assertEqual(401, response.code) def test_valid_creds_pass(self): response = self.fetch( '/evaluate', method='POST', body=self.script, headers={ 'Authorization': 'Basic {}'.format( base64.b64encode( 'username:password'.encode('utf-8')).decode('utf-8')) }) self.assertEqual(200, response.code) def test_null_request(self): response = self.fetch('') self.assertEqual(404, response.code) def test_script_not_present(self): response = self.fetch( '/evaluate', method='POST', body=self.script_not_present, headers={ 'Authorization': 'Basic {}'.format( base64.b64encode( 'username:password'.encode('utf-8')).decode('utf-8')) }) self.assertEqual(400, response.code) def test_arguments_not_present(self): response = self.fetch( '/evaluate', method='POST', body=self.args_not_present, headers={ 'Authorization': 'Basic {}'.format( base64.b64encode( 'username:password'.encode('utf-8')).decode('utf-8')) }) self.assertEqual(500, response.code) def test_arguments_not_sequential(self): response = self.fetch( '/evaluate', method='POST', body=self.args_not_sequential, headers={ 'Authorization': 'Basic {}'.format( base64.b64encode( 'username:password'.encode('utf-8')).decode('utf-8')) }) self.assertEqual(400, response.code)
def main(): app = TabPyApp() app.run()
def get_app(self): self.app = TabPyApp() return self.app._create_tornado_web_app()
class TestEndpointHandlerWithAuth(AsyncHTTPTestCase): @classmethod def setUpClass(cls): cls.patcher = patch( 'tabpy_server.app.app.TabPyApp._parse_cli_arguments', return_value=Namespace( config=None)) cls.patcher.start() prefix = '__TestEndpointHandlerWithAuth_' # create password file cls.pwd_file = tempfile.NamedTemporaryFile( mode='w+t', prefix=prefix, suffix='.txt', delete=False) username = '******' password = '******' cls.pwd_file.write('{} {}'.format( username, hash_password(username, password))) cls.pwd_file.close() # create state.ini dir and file cls.state_dir = tempfile.mkdtemp(prefix=prefix) cls.state_file = open(os.path.join(cls.state_dir, 'state.ini'), 'w+') cls.state_file.write('[Service Info]\n' 'Name = TabPy Serve\n' 'Description = \n' 'Creation Time = 0\n' 'Access-Control-Allow-Origin = \n' 'Access-Control-Allow-Headers = \n' 'Access-Control-Allow-Methods = \n' '\n' '[Query Objects Service Versions]\n' '\n' '[Query Objects Docstrings]\n' '\n' '[Meta]\n' 'Revision Number = 1\n') cls.state_file.close() # create config file cls.config_file = tempfile.NamedTemporaryFile( mode='w+t', prefix=prefix, suffix='.conf', delete=False) cls.config_file.write( '[TabPy]\n' 'TABPY_PWD_FILE = {}\n' 'TABPY_STATE_PATH = {}'.format( cls.pwd_file.name, cls.state_dir)) cls.config_file.close() @classmethod def tearDownClass(cls): cls.patcher.stop() os.remove(cls.pwd_file.name) os.remove(cls.state_file.name) os.remove(cls.config_file.name) os.rmdir(cls.state_dir) def get_app(self): self.app = TabPyApp(self.config_file.name) return self.app._create_tornado_web_app() def test_no_creds_required_auth_fails(self): response = self.fetch('/endpoints/anything') self.assertEqual(401, response.code) def test_invalid_creds_fails(self): response = self.fetch( '/endpoints/anything', method='GET', headers={ 'Authorization': 'Basic {}'. format( base64.b64encode('user:wrong_password'.encode('utf-8')). decode('utf-8')) }) self.assertEqual(401, response.code) def test_valid_creds_pass(self): response = self.fetch( '/endpoints/', method='GET', headers={ 'Authorization': 'Basic {}'. format( base64.b64encode('username:password'.encode('utf-8')). decode('utf-8')) }) self.assertEqual(200, response.code) def test_valid_creds_unknown_endpoint_fails(self): response = self.fetch( '/endpoints/unknown_endpoint', method='GET', headers={ 'Authorization': 'Basic {}'. format( base64.b64encode('username:password'.encode('utf-8')). decode('utf-8')) }) self.assertEqual(404, response.code)