Skip to content

Commit

Permalink
Merge pull request #192 from tableau/dev_validate_creds
Browse files Browse the repository at this point in the history
Dev validate creds
  • Loading branch information
0golovatyi committed Feb 21, 2019
2 parents 0a2210d + 0bdbc8f commit a097f7e
Show file tree
Hide file tree
Showing 8 changed files with 431 additions and 29 deletions.
19 changes: 19 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,22 @@ Access-Control-Allow-Methods = GET, OPTIONS, POST
- Open `misc/TabPy.yml` in Swagger editor.
- In case your TabPy server runs not on `localhost:9004` update
`host` value in `TabPy.yml` accordingly.

## Code styling

On github repo for merge request `pycodestyle` is used to check Python code against our
style conventions. You can run install it and run locally for file where modifications
were made:

```sh
pip install pycodestyle
pycodestyle <file.py>
```

For reported errors and warnings either fix them manually or auto-format files with
`autopep8`:

```sh
pip install autopep8
autopep8 -i <file.py>
```
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
[![Build Status](https://travis-ci.com/tableau/TabPy.svg?branch=master)](https://travis-ci.com/tableau/TabPy)
[![Coverage Status](https://coveralls.io/repos/github/tableau/TabPy/badge.svg)](https://coveralls.io/github/tableau/TabPy)

[![Python 3.6](https://img.shields.io/badge/python-3.6-blue.svg)](https://www.python.org/downloads/release/python-360/)

TabPy (Tableau Python Server) is external server implementation which allows expanding Tableau with executing Python scripts on table calculation.

All documentation is in the [docs](docs) folder. Consider reading it the next order:
Expand Down
61 changes: 39 additions & 22 deletions tabpy-server/server_tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,23 @@ def assert_raises_runtime_error(message, fn, args={}):


class TestConfigEnvironmentCalls(unittest.TestCase):

@patch('tabpy_server.app.app.TabPyApp._parse_cli_arguments', return_value = Namespace(config=None))
@patch('tabpy_server.app.app.TabPyApp._parse_cli_arguments',
return_value=Namespace(config=None))
@patch('tabpy_server.app.app.TabPyState')
@patch('tabpy_server.app.app._get_state_from_file')
@patch('tabpy_server.app.app.PythonServiceHandler')
@patch('tabpy_server.app.app.os.path.exists', return_value=True)
@patch('tabpy_server.app.app.os.path.isfile', return_value=False)
@patch('tabpy_server.app.app.os')
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):
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', './')]
call('TABPY_QUERY_OBJECT_PATH', '/tmp/query_objects'),
call('TABPY_STATE_PATH', './')]
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)
Expand All @@ -43,28 +46,32 @@ def test_no_config_file(self, mock_os, mock_file_exists, mock_path_exists, mock_
self.assertTrue(len(mock_management_util.mock_calls) > 0)
mock_os.makedirs.assert_not_called()

@patch('tabpy_server.app.app.TabPyApp._parse_cli_arguments', return_value = Namespace(config=None))
@patch('tabpy_server.app.app.TabPyApp._parse_cli_arguments',
return_value=Namespace(config=None))
@patch('tabpy_server.app.app.TabPyState')
@patch('tabpy_server.app.app._get_state_from_file')
@patch('tabpy_server.app.app.PythonServiceHandler')
@patch('tabpy_server.app.app.os.path.exists', return_value=False)
@patch('tabpy_server.app.app.os.path.isfile', return_value=False)
@patch('tabpy_server.app.app.os')
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):
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)


class TestPartialConfigFile(unittest.TestCase):

@patch('tabpy_server.app.app.TabPyApp._parse_cli_arguments')
@patch('tabpy_server.app.app.TabPyState')
@patch('tabpy_server.app.app._get_state_from_file')
@patch('tabpy_server.app.app.PythonServiceHandler')
@patch('tabpy_server.app.app.os.path.exists', return_value=True)
@patch('tabpy_server.app.app.os')
def test_config_file_present(self, mock_os, mock_path_exists, mock_psws, mock_management_util,
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)

Expand All @@ -83,9 +90,10 @@ def test_config_file_present(self, mock_os, mock_path_exists, mock_psws, mock_ma

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.assertEquals(app.settings['upload_dir'], 'foo')
self.assertEquals(app.settings['state_file_path'], 'bar')
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)
Expand Down Expand Up @@ -133,7 +141,8 @@ def test_https_without_cert_and_key(self):
self.fp.close()

assert_raises_runtime_error(
'Error using HTTPS: The parameter(s) TABPY_CERTIFICATE_FILE and TABPY_KEY_FILE must be set.',
'Error using HTTPS: The parameter(s) TABPY_CERTIFICATE_FILE '
'and TABPY_KEY_FILE must be set.',
TabPyApp, {self.fp.name})

def test_https_without_cert(self):
Expand All @@ -143,7 +152,8 @@ def test_https_without_cert(self):
"TABPY_KEY_FILE = foo")
self.fp.close()

assert_raises_runtime_error('Error using HTTPS: The parameter(s) TABPY_CERTIFICATE_FILE must be set.',
assert_raises_runtime_error('Error using HTTPS: The parameter(s) '
'TABPY_CERTIFICATE_FILE must be set.',
TabPyApp, {self.fp.name})

def test_https_without_key(self):
Expand All @@ -152,7 +162,8 @@ def test_https_without_key(self):
"TABPY_CERTIFICATE_FILE = foo")
self.fp.close()

assert_raises_runtime_error('Error using HTTPS: The parameter(s) TABPY_KEY_FILE must be set.',
assert_raises_runtime_error('Error using HTTPS: The parameter(s) '
'TABPY_KEY_FILE must be set.',
TabPyApp, {self.fp.name})

@patch('tabpy_server.app.app.os.path')
Expand All @@ -163,10 +174,12 @@ def test_https_cert_and_key_file_not_found(self, mock_path):
"TABPY_KEY_FILE = bar")
self.fp.close()

mock_path.isfile.side_effect = lambda x: self.mock_isfile(x, {self.fp.name})
mock_path.isfile.side_effect =\
lambda x: self.mock_isfile(x, {self.fp.name})

assert_raises_runtime_error(
'Error using HTTPS: The parameter(s) TABPY_CERTIFICATE_FILE and TABPY_KEY_FILE must point to an existing file.',
'Error using HTTPS: The parameter(s) TABPY_CERTIFICATE_FILE '
'and TABPY_KEY_FILE must point to an existing file.',
TabPyApp, {self.fp.name})

@patch('tabpy_server.app.app.os.path')
Expand All @@ -181,7 +194,8 @@ def test_https_cert_file_not_found(self, mock_path):
x, {self.fp.name, 'bar'})

assert_raises_runtime_error(
'Error using HTTPS: The parameter(s) TABPY_CERTIFICATE_FILE must point to an existing file.',
'Error using HTTPS: The parameter(s) TABPY_CERTIFICATE_FILE '
'must point to an existing file.',
TabPyApp, {self.fp.name})

@patch('tabpy_server.app.app.os.path')
Expand All @@ -196,7 +210,8 @@ def test_https_key_file_not_found(self, mock_path):
x, {self.fp.name, 'foo'})

assert_raises_runtime_error(
'Error using HTTPS: The parameter(s) TABPY_KEY_FILE must point to an existing file.', TabPyApp, {self.fp.name})
'Error using HTTPS: The parameter(s) TABPY_KEY_FILE must '
'point to an existing file.', TabPyApp, {self.fp.name})

@patch('tabpy_server.app.app.os.path.isfile', return_value=True)
@patch('tabpy_server.app.util.validate_cert')
Expand All @@ -223,12 +238,14 @@ def __init__(self, *args, **kwargs):

def test_expired_cert(self):
path = os.path.join(self.resources_path, 'expired.crt')
message = 'Error using HTTPS: The certificate provided expired on 2018-08-18 19:47:18.'
message = 'Error using HTTPS: The certificate provided expired '\
'on 2018-08-18 19:47:18.'
assert_raises_runtime_error(message, validate_cert, {path})

def test_future_cert(self):
path = os.path.join(self.resources_path, 'future.crt')
message = 'Error using HTTPS: The certificate provided is not valid until 3001-01-01 00:00:00.'
message = 'Error using HTTPS: The certificate provided is not '\
'valid until 3001-01-01 00:00:00.'
assert_raises_runtime_error(message, validate_cert, {path})

def test_valid_cert(self):
Expand Down
23 changes: 19 additions & 4 deletions tabpy-server/server_tests/test_pwd_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,33 @@ def test_given_one_login_many_times_in_pwd_file_expect_app_fails(self):
self.assertEqual('Failed to read password file {}'.format(
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_multiple_credentials_expect_all_parsed(self):
self._set_file(self.config_file.name,
"[TabPy]\n"
"TABPY_PWD_FILE = {}".format(self.pwd_file.name))
creds = {
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])
Expand All @@ -124,4 +140,3 @@ def test_given_multiple_credentials_expect_all_parsed(self):
for login in creds:
self.assertIn(login, app.credentials)
self.assertEqual(creds[login], app.credentials[login])

0 comments on commit a097f7e

Please sign in to comment.