From 8fdf7acb72f6f540caeb44ec987bd60928fc93a1 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Wed, 1 Mar 2023 14:04:58 -0800 Subject: [PATCH 1/9] implement proxy option --- tabcmd/commands/auth/session.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/tabcmd/commands/auth/session.py b/tabcmd/commands/auth/session.py index 1dcf34a7..6e03d4fa 100644 --- a/tabcmd/commands/auth/session.py +++ b/tabcmd/commands/auth/session.py @@ -152,18 +152,23 @@ def _create_new_token_credential(self): def _set_connection_options(self) -> TSC.Server: self.logger.debug("Setting up request options") - # args still to be handled here: - # proxy, --no-proxy, - # cert http_options: Dict[str, Any] = {"headers": {"User-Agent": "Tabcmd/{}".format(version)}} + if self.no_certcheck: http_options["verify"] = False urllib3.disable_warnings(category=InsecureRequestWarning) - if self.proxy: + + if self.proxy and not self.no_proxy: # do we catch this error? "sessionoptions.errors.bad_proxy_format" self.logger.debug("Setting proxy: ", self.proxy) + http_options['proxy'] = self.proxy + if self.timeout: http_options["timeout"] = self.timeout + + if self.certificate: + print("# do something") + try: self.logger.debug(http_options) tableau_server = TSC.Server(self.server_url, http_options=http_options) @@ -245,12 +250,17 @@ def _sign_in(self, tableau_auth) -> TSC.Server: self.site_id = self.tableau_server.site_id self.user_id = self.tableau_server.user_id self.auth_token = self.tableau_server._auth_token + if not self.username: self.username = self.tableau_server.users.get_by_id(self.user_id).name - self.logger.info(_("common.output.succeeded")) + success = self._validate_existing_signin() except Exception as e: Errors.exit_with_error(self.logger, exception=e) - self.logger.debug("Signed into {0}{1} as {2}".format(self.server_url, self.site_name, self.username)) + if success: + self.logger.info(_("common.output.succeeded")) + else: + Errors.exit_with_error(self.logger, message="Sign in failed") + return self.tableau_server def _get_saved_credentials(self): From 4cf0bb374f91a05d7854a4b73a1d3d0af6a5d6ea Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Thu, 9 Mar 2023 15:38:33 -0800 Subject: [PATCH 2/9] connection options for proxy, client certs implement --proxy, --no-proxy options implement but not tested --use-certificate option --- tabcmd/commands/auth/session.py | 52 ++++++++++++++++++++----------- tabcmd/execution/parent_parser.py | 2 +- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/tabcmd/commands/auth/session.py b/tabcmd/commands/auth/session.py index 6e03d4fa..7417b43d 100644 --- a/tabcmd/commands/auth/session.py +++ b/tabcmd/commands/auth/session.py @@ -43,7 +43,7 @@ def __init__(self): self.no_prompt = False self.certificate = None self.no_certcheck = False - self.no_proxy = True + self.no_proxy = False self.proxy = None self.timeout = None @@ -73,10 +73,10 @@ def _update_session_data(self, args): self.token_name = args.token_name or self.token_name self.token_value = args.token_value or self.token_value - self.no_prompt = args.no_prompt or self.no_prompt - self.certificate = args.certificate or self.certificate - self.no_certcheck = args.no_certcheck or self.no_certcheck - self.no_proxy = args.no_proxy or self.no_proxy + self.no_prompt = args.no_prompt # have to set this on every call? + self.certificate = args.use_certificate or self.certificate + self.no_certcheck = args.no_certcheck # have to set this on every call? + self.no_proxy = args.no_proxy # have to set this on every call? self.proxy = args.proxy or self.proxy self.timeout = self.timeout_as_integer(self.logger, args.timeout, self.timeout) @@ -158,25 +158,39 @@ def _set_connection_options(self) -> TSC.Server: http_options["verify"] = False urllib3.disable_warnings(category=InsecureRequestWarning) - if self.proxy and not self.no_proxy: - # do we catch this error? "sessionoptions.errors.bad_proxy_format" - self.logger.debug("Setting proxy: ", self.proxy) - http_options['proxy'] = self.proxy + """ + Do we want to do the same format check as old tabcmd? + For now I think we can trust requests to handle a bad proxy + Pattern pattern = Pattern.compile("([^:]*):([0-9]*)"); + if not matches: + throw new ReportableException(m_i18n.getString("sessionoptions.errors.bad_proxy_format", proxyArg)); + """ + if self.proxy: + self.logger.debug("Setting http proxy: {}".format(self.proxy)) + proxies = { + 'http': self.proxy + } + http_options['proxies'] = proxies + if self.no_proxy: + # override any proxy that was set + http_options['proxies'] = None if self.timeout: http_options["timeout"] = self.timeout if self.certificate: - print("# do something") + http_options["cert"] = self.certificate try: self.logger.debug(http_options) + # this is the only place we open a connection to the server + # so the request options are all set for the session now tableau_server = TSC.Server(self.server_url, http_options=http_options) except Exception as e: self.logger.debug( - "Connection args: server {}, site {}, proxy {}, cert {}".format( - self.server_url, self.site_name, self.proxy, self.certificate + "Connection args: server {}, site {}, proxy {}/no-proxy {}, cert {}".format( + self.server_url, self.site_name, self.proxy, self.certificate, self.no_proxy ) ) Errors.exit_with_error(self.logger, "Failed to connect to server", e) @@ -201,7 +215,7 @@ def _verify_server_connection_unauthed(self): Errors.exit_with_error(self.logger, exception=e) def _create_new_connection(self) -> TSC.Server: - self.logger.info(_("session.new_session")) + # self.logger.info(_("session.new_session")) self._print_server_info() self.logger.info(_("session.connecting")) try: @@ -216,6 +230,10 @@ def _read_existing_state(self): def _print_server_info(self): self.logger.info("===== Server: {}".format(self.server_url)) + if self.proxy: + self.logger.info("===== Proxy: {}".format(self.proxy)) + if self.no_proxy: + self.logger.info("===== Proxy Override: set proxy to None") if self.username: self.logger.info("===== Username: {}".format(self.username)) else: @@ -228,10 +246,8 @@ def _validate_existing_signin(self): # when do these two messages show up? self.logger.info(_("session.auto_site_login")) try: if self.tableau_server and self.tableau_server.is_signed_in(): - response = self.tableau_server.users.get_by_id(self.user_id) - self.logger.debug(response) - if response.status_code.startswith("200"): - return self.tableau_server + self.username = self.tableau_server.users.get_by_id(self.user_id).name + return self.tableau_server except TSC.ServerResponseError as e: self.logger.info(_("publish.errors.unexpected_server_response"), e) except Exception as e: @@ -251,8 +267,6 @@ def _sign_in(self, tableau_auth) -> TSC.Server: self.user_id = self.tableau_server.user_id self.auth_token = self.tableau_server._auth_token - if not self.username: - self.username = self.tableau_server.users.get_by_id(self.user_id).name success = self._validate_existing_signin() except Exception as e: Errors.exit_with_error(self.logger, exception=e) diff --git a/tabcmd/execution/parent_parser.py b/tabcmd/execution/parent_parser.py index ba379619..a26d3f3f 100644 --- a/tabcmd/execution/parent_parser.py +++ b/tabcmd/execution/parent_parser.py @@ -85,7 +85,7 @@ def parent_parser_with_global_options(): ) proxy_group.add_argument( "--no-proxy", - action="store_false", + action="store_true", #default to false help=_("session.options.no-proxy"), ) From f42173510c576c5ab00c179ec09edf8a47b94cb8 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Fri, 10 Mar 2023 01:59:48 -0800 Subject: [PATCH 3/9] format, update tests --- tabcmd/commands/auth/session.py | 39 +++++-- tabcmd/execution/parent_parser.py | 2 +- tests/commands/test_session.py | 183 ++++++++++++++++-------------- 3 files changed, 124 insertions(+), 100 deletions(-) diff --git a/tabcmd/commands/auth/session.py b/tabcmd/commands/auth/session.py index 7417b43d..91554874 100644 --- a/tabcmd/commands/auth/session.py +++ b/tabcmd/commands/auth/session.py @@ -150,7 +150,7 @@ def _create_new_token_credential(self): else: Errors.exit_with_error(self.logger, _("session.errors.missing_arguments").format("token name")) - def _set_connection_options(self) -> TSC.Server: + def _open_connection_with_opts(self) -> TSC.Server: self.logger.debug("Setting up request options") http_options: Dict[str, Any] = {"headers": {"User-Agent": "Tabcmd/{}".format(version)}} @@ -167,13 +167,11 @@ def _set_connection_options(self) -> TSC.Server: """ if self.proxy: self.logger.debug("Setting http proxy: {}".format(self.proxy)) - proxies = { - 'http': self.proxy - } - http_options['proxies'] = proxies + proxies = {"http": self.proxy} + http_options["proxies"] = proxies if self.no_proxy: # override any proxy that was set - http_options['proxies'] = None + http_options["proxies"] = None if self.timeout: http_options["timeout"] = self.timeout @@ -219,7 +217,7 @@ def _create_new_connection(self) -> TSC.Server: self._print_server_info() self.logger.info(_("session.connecting")) try: - self.tableau_server = self._set_connection_options() + self.tableau_server = self._open_connection_with_opts() except Exception as e: Errors.exit_with_error(self.logger, "Failed to connect to server", e) return self.tableau_server @@ -241,12 +239,21 @@ def _print_server_info(self): site_display_name = self.site_name or "Default Site" self.logger.info(_("dataconnections.classes.tableau_server_site") + ": {}".format(site_display_name)) + # side-effect: sets self.username def _validate_existing_signin(self): - self.logger.info(_("session.continuing_session")) # when do these two messages show up? self.logger.info(_("session.auto_site_login")) try: if self.tableau_server and self.tableau_server.is_signed_in(): - self.username = self.tableau_server.users.get_by_id(self.user_id).name + server_user = self.tableau_server.users.get_by_id(self.user_id).name + if not self.username: + self.username = server_user + if not self.username == server_user: + Errors.exit_with_error( + self.logger, + message="Local username `{}` does not match server username `{}`".format( + self.username, server_user + ), + ) return self.tableau_server except TSC.ServerResponseError as e: self.logger.info(_("publish.errors.unexpected_server_response"), e) @@ -266,7 +273,6 @@ def _sign_in(self, tableau_auth) -> TSC.Server: self.site_id = self.tableau_server.site_id self.user_id = self.tableau_server.user_id self.auth_token = self.tableau_server._auth_token - success = self._validate_existing_signin() except Exception as e: Errors.exit_with_error(self.logger, exception=e) @@ -314,6 +320,7 @@ def create_session(self, args, logger): else: # no login arguments given - look for saved info # maybe we're already signed in! if self.tableau_server: + self.logger.info(_("session.continuing_session")) signed_in_object = self._validate_existing_signin() self.logger.debug(signed_in_object) # or maybe we at least have the credentials saved @@ -321,9 +328,9 @@ def create_session(self, args, logger): credentials = self._get_saved_credentials() if credentials and not signed_in_object: - # logging in, not using an existing session + self.logger.debug("We are not logged in yet but we have info to log in with") self.tableau_server = self._create_new_connection() - self._verify_server_connection_unauthed() + self._verify_server_connection_unauthed() # not sure if this line is necessary signed_in_object = self._sign_in(credentials) if not signed_in_object: @@ -382,7 +389,12 @@ def _read_from_json(self): try: with open(str(file_path), "r") as file_contents: data = json.load(file_contents) + if data is None or data == {}: + return content = data["tableau_auth"] + if content is None: + return + self._save_data_from_json(content) except json.JSONDecodeError as e: self._wipe_bad_json(e, "Error reading data from session file") except IOError as e: @@ -392,8 +404,11 @@ def _read_from_json(self): except Exception as e: self._wipe_bad_json(e, "Unexpected error reading session details from file") + def _save_data_from_json(self, content): try: auth = content[0] + if auth is None: + self._wipe_bad_json(ValueError(), "Empty session file") self.auth_token = auth["auth_token"] self.server_url = auth["server"] self.site_name = auth["site_name"] diff --git a/tabcmd/execution/parent_parser.py b/tabcmd/execution/parent_parser.py index a26d3f3f..bc81f59c 100644 --- a/tabcmd/execution/parent_parser.py +++ b/tabcmd/execution/parent_parser.py @@ -85,7 +85,7 @@ def parent_parser_with_global_options(): ) proxy_group.add_argument( "--no-proxy", - action="store_true", #default to false + action="store_true", # default to false help=_("session.options.no-proxy"), ) diff --git a/tests/commands/test_session.py b/tests/commands/test_session.py index 5f4c8297..d08a6aa3 100644 --- a/tests/commands/test_session.py +++ b/tests/commands/test_session.py @@ -2,10 +2,9 @@ from argparse import Namespace import unittest from unittest import mock -from unittest.mock import patch, mock_open +from unittest.mock import MagicMock from tabcmd.commands.auth.session import Session -import os args_to_mock = Namespace( username=None, @@ -19,7 +18,7 @@ no_certcheck=None, no_prompt=False, no_cookie=False, - certificate=None, + use_certificate=None, proxy=None, no_proxy=True, timeout=None, @@ -50,9 +49,9 @@ logger = logging.getLogger("tests") -def _set_mocks_for_json_file_saved_username(mock_json_load, auth_token, username): +def _set_mocks_for_json_file_saved_username(mock_json_lib, auth_token, username): mock_auth = vars(mock_data_from_json) - mock_json_load.return_value = {"tableau_auth": [mock_auth]} + mock_json_lib.load.return_value = {"tableau_auth": [mock_auth]} mock_auth["auth_token"] = auth_token mock_auth["username"] = username mock_auth["server"] = fakeserver @@ -60,27 +59,28 @@ def _set_mocks_for_json_file_saved_username(mock_json_load, auth_token, username mock_auth["no_certcheck"] = True -def _set_mocks_for_json_file_exists(mock_path, does_it_exist=True): - os.path = mock_path - mock_path.expanduser.return_value = "" - mock_path.join.return_value = "" - mock_path.exists.return_value = does_it_exist - return mock_path - +def _set_mocks_for_json_file_exists(mock_path, mock_json_lib, does_it_exist=True): + mock_json_lib.JSONDecodeError = ValueError + path = mock_path() + path.expanduser.return_value = "" + path.join.return_value = "" + path.exists.return_value = does_it_exist -def _set_mock_file_content(mock_load, expected_content): - mock_load.return_value = expected_content - return mock_load + mock_auth = vars(mock_data_from_json) + if does_it_exist: + mock_json_lib.load.return_value = [mock_auth] + else: + mock_json_lib.load.return_value = None + return path -@mock.patch("json.dump") -@mock.patch("json.load") +@mock.patch("tabcmd.commands.auth.session.json") @mock.patch("os.path") @mock.patch("builtins.open") class JsonTests(unittest.TestCase): - def test_read_session_from_json(self, mock_open, mock_path, mock_load, mock_dump): - _set_mocks_for_json_file_exists(mock_path) - _set_mocks_for_json_file_saved_username(mock_load, "AUTHTOKEN", "USERNAME") + def test_read_session_from_json(self, mock_open, mock_path, mock_json): + _set_mocks_for_json_file_exists(mock_path, mock_json) + _set_mocks_for_json_file_saved_username(mock_json, "AUTHTOKEN", "USERNAME") test_session = Session() test_session._read_from_json() assert hasattr(test_session.auth_token, "AUTHTOKEN") is False, test_session @@ -88,16 +88,16 @@ def test_read_session_from_json(self, mock_open, mock_path, mock_load, mock_dump assert test_session.username == "USERNAME" assert test_session.server_url == fakeserver, test_session.server_url - def test_save_session_to_json(self, mock_open, mock_path, mock_load, mock_dump): - _set_mocks_for_json_file_exists(mock_path) + def test_save_session_to_json(self, mock_open, mock_path, mock_json): + _set_mocks_for_json_file_exists(mock_path, mock_json) test_session = Session() test_session.username = "USN" test_session.server = "SRVR" test_session._save_session_to_json() - assert mock_dump.was_called() + assert mock_json.dump.was_called() - def clear_session(self, mock_open, mock_path, mock_load, mock_dump): - _set_mocks_for_json_file_exists(mock_path) + def clear_session(self, mock_open, mock_path, mock_json): + _set_mocks_for_json_file_exists(mock_path, mock_json) test_session = Session() test_session.username = "USN" test_session.server = "SRVR" @@ -105,13 +105,13 @@ def clear_session(self, mock_open, mock_path, mock_load, mock_dump): assert test_session.username is None assert test_session.server is None - def test_json_not_present(self, mock_open, mock_path, mock_load, mock_dump): - _set_mocks_for_json_file_exists(mock_path, False) + def test_json_not_present(self, mock_open, mock_path, mock_json): + _set_mocks_for_json_file_exists(mock_path, mock_json, does_it_exist=False) assert mock_open.was_not_called() - def test_json_invalid(self, mock_open, mock_path, mock_load, mock_dump): - _set_mocks_for_json_file_exists(mock_path) - _set_mock_file_content(mock_load, "just a string") + def test_json_invalid(self, mock_open, mock_path, mock_json): + _set_mocks_for_json_file_exists(mock_path, mock_json) + mock_json.load = "just a string" test_session = Session() assert test_session.username is None @@ -215,34 +215,30 @@ def test_dont_show_prompt_if_user_said_no(self): """ -@mock.patch("json.dump") -@mock.patch("json.load") +@mock.patch("tabcmd.commands.auth.session.json") @mock.patch("os.path") @mock.patch("builtins.open") @mock.patch("getpass.getpass") class CreateSessionTests(unittest.TestCase): @mock.patch("tableauserverclient.Server") - def test_create_session_first_time_no_args( - self, mock_tsc, mock_pass, mock_file, mock_path, mock_json_load, mock_json_dump - ): - mock_path = _set_mocks_for_json_file_exists(mock_path, False) - assert mock_path.exists("anything") is False + def test_create_session_first_time_no_args(self, mock_tsc, mock_pass, mock_file, mock_path, mock_json): + _set_mocks_for_json_file_exists(mock_path, mock_json, does_it_exist=False) + test_args = Namespace(**vars(args_to_mock)) - mock_tsc().users.get_by_id.return_value = None new_session = Session() with self.assertRaises(SystemExit): auth = new_session.create_session(test_args, None) @mock.patch("tableauserverclient.Server") - def test_create_session_first_time_with_token_arg( - self, mock_tsc, mock_pass, mock_file, mock_path, mock_json_load, mock_json_dump - ): - mock_path = _set_mocks_for_json_file_exists(mock_path, False) - assert mock_path.exists("anything") is False + def test_create_session_first_time_with_token_arg(self, mock_tsc, mock_pass, mock_file, mock_path, mock_json): + _set_mocks_for_json_file_exists(mock_path, mock_json, does_it_exist=False) + new_session = Session() + new_session.tableau_server = mock_tsc() + _set_mock_signin_validation_succeeds(new_session.tableau_server, "u") + test_args = Namespace(**vars(args_to_mock)) test_args.token_name = "tn" test_args.token_value = "foo" - new_session = Session() auth = new_session.create_session(test_args, None) assert auth is not None, auth assert auth.auth_token is not None, auth.auth_token @@ -251,46 +247,52 @@ def test_create_session_first_time_with_token_arg( assert new_session.token_name == "tn", new_session @mock.patch("tableauserverclient.Server") - def test_create_session_first_time_with_password_arg( - self, mock_tsc, mock_pass, mock_file, mock_path, mock_json_load, mock_json_dump - ): - mock_path = _set_mocks_for_json_file_exists(mock_path, False) + def test_create_session_first_time_with_password_arg(self, mock_tsc, mock_pass, mock_file, mock_path, mock_json): + name = "uuuu" + new_session = Session() + new_session.tableau_server = mock_tsc() + _set_mocks_for_json_file_exists(mock_path, mock_json, does_it_exist=False) + _set_mock_signin_validation_succeeds(new_session.tableau_server, name) + test_args = Namespace(**vars(args_to_mock)) - test_args.username = "uuuu" + test_args.username = name test_args.password = "pppp" - new_session = Session() auth = new_session.create_session(test_args, None) assert auth is not None, auth assert auth.auth_token is not None, auth.auth_token - assert new_session.username == "uuuu", new_session + assert new_session.username == name, new_session assert mock_tsc.has_been_called() @mock.patch("tableauserverclient.Server") def test_create_session_first_time_with_password_file_as_password( - self, mock_tsc, mock_pass, mock_file, mock_path, mock_json_load, mock_json_dump + self, mock_tsc, mock_pass, mock_file, mock_path, mock_json ): - mock_path = _set_mocks_for_json_file_exists(mock_path, False) + username = "uuuu" + _set_mocks_for_json_file_exists(mock_path, mock_json, does_it_exist=False) + new_session = Session() + _set_mock_signin_validation_succeeds(mock_tsc(), username) test_args = Namespace(**vars(args_to_mock)) - test_args.username = "uuuu" + test_args.username = username # filename = os.path.join(os.path.dirname(__file__),"test_credential_file.txt") # test_args.password_file = os.getcwd()+"/test_credential_file.txt" test_args.password_file = "filename" with mock.patch("builtins.open", mock.mock_open(read_data="my_password")): - new_session = Session() auth = new_session.create_session(test_args, None) assert auth is not None, auth assert auth.auth_token is not None, auth.auth_token - assert new_session.username == "uuuu", new_session + assert new_session.username == username, new_session assert new_session.password_file == "filename", new_session assert mock_tsc.has_been_called() @mock.patch("tableauserverclient.Server") def test_create_session_first_time_with_password_file_as_token( - self, mock_tsc, mock_pass, mock_file, mock_path, mock_json_load, mock_json_dump + self, mock_tsc, mock_pass, mock_file, mock_path, mock_json ): - mock_path = _set_mocks_for_json_file_exists(mock_path, False) + _set_mocks_for_json_file_exists(mock_path, mock_json, does_it_exist=False) + server = mock_tsc() + _set_mock_signin_validation_succeeds(server, "testing") test_args = Namespace(**vars(args_to_mock)) test_args.token_name = "mytoken" test_args.password_file = "filename" @@ -304,9 +306,9 @@ def test_create_session_first_time_with_password_file_as_token( assert mock_tsc.has_been_called() @mock.patch("tableauserverclient.Server") - def test_load_saved_session_data(self, mock_tsc, mock_pass, mock_file, mock_path, mock_json_load, mock_json_dump): - _set_mocks_for_json_file_exists(mock_path, True) - _set_mocks_for_json_file_saved_username(mock_json_load, "auth_token", "username") + def test_load_saved_session_data(self, mock_tsc, mock_pass, mock_file, mock_path, mock_json): + _set_mocks_for_json_file_exists(mock_path, mock_json) + _set_mocks_for_json_file_saved_username(mock_json, "auth_token", "username") test_args = Namespace(**vars(args_to_mock)) new_session = Session() new_session._read_existing_state() @@ -317,11 +319,9 @@ def test_load_saved_session_data(self, mock_tsc, mock_pass, mock_file, mock_path assert mock_tsc.has_been_called() @mock.patch("tableauserverclient.Server") - def test_create_session_with_active_session_saved( - self, mock_tsc, mock_pass, mock_file, mock_path, mock_json_load, mock_json_dump - ): - _set_mocks_for_json_file_exists(mock_path, True) - _set_mocks_for_json_file_saved_username(mock_json_load, "auth_token", None) + def test_create_session_with_active_session_saved(self, mock_tsc, mock_pass, mock_file, mock_path, mock_json): + _set_mocks_for_json_file_exists(mock_path, mock_json) + _set_mocks_for_json_file_saved_username(mock_json, "auth_token", None) test_args = Namespace(**vars(args_to_mock)) test_args.token_value = "tn" test_args.token_name = "tnnnn" @@ -335,12 +335,14 @@ def test_create_session_with_active_session_saved( @mock.patch("tableauserverclient.Server") def test_create_session_with_saved_expired_username_session( - self, mock_tsc, mock_pass, mock_file, mock_path, mock_json_load, mock_json_dump + self, mock_tsc, mock_pass, mock_file, mock_path, mock_json ): - _set_mocks_for_json_file_saved_username(mock_json_load, "auth_token", "username") - _set_mocks_for_json_file_exists(mock_path, True) - tsc_under_test = CreateSessionTests._set_mock_tsc_not_signed_in(mock_tsc) - CreateSessionTests._set_mock_tsc_sign_in_succeeds(tsc_under_test) + test_username = "monster" + server = mock_tsc() + _set_mocks_for_json_file_exists(mock_path, mock_json) + _set_mocks_for_json_file_saved_username(mock_json, "auth_token", test_username) + _set_mock_tsc_sign_in_succeeds(server) + _set_mock_signin_validation_succeeds(server, test_username) test_args = Namespace(**vars(args_to_mock)) mock_pass.getpass.return_value = "success" test_args.password = "eqweqwe" @@ -350,24 +352,31 @@ def test_create_session_with_saved_expired_username_session( assert auth is not None, auth assert auth.auth_token is not None, auth.auth_token assert auth.auth_token == "cookiieeeee" - assert new_session.username == "username", new_session + assert new_session.username == test_username, new_session assert mock_tsc.use_server_version.has_been_called() - @staticmethod - def _set_mock_tsc_not_signed_in(mock_tsc): - tsc_in_test = mock.MagicMock(name="manually mocking tsc") - mock_tsc.return_value = tsc_in_test - tsc_in_test.is_signed_in.return_value = False # CreateSessionTests.return_False - tsc_in_test.server_info.get.return_value = Exception - return tsc_in_test - @staticmethod - def _set_mock_tsc_sign_in_succeeds(mock_tsc): - tscauth_mock = mock.MagicMock(name="tsc.auth") - mock_tsc.auth = tscauth_mock - mock_tsc.auth_token = "cookiieeeee" - mock_tsc.site_id = "1" - mock_tsc.user_id = "0" +def _set_mock_tsc_not_signed_in(mock_tsc): + tsc_in_test = mock.MagicMock(name="manually mocking tsc") + tsc_in_test.is_signed_in.return_value = False # CreateSessionTests.return_False + tsc_in_test.server_info.get.return_value = Exception + return tsc_in_test + + +def _set_mock_tsc_sign_in_succeeds(mock_tsc): + tscauth_mock = mock.MagicMock(name="tsc.auth") + mock_tsc.auth = tscauth_mock + mock_tsc.auth_token = "cookiieeeee" + mock_tsc.site_id = "1" + mock_tsc.user_id = "0" + + +def _set_mock_signin_validation_succeeds(mock_tsc, username): + mock_u_factory = MagicMock("user") + mock_u = mock_u_factory() + mock_tsc.users.get_by_id.return_value = mock_u + mock_u.name = username + mock_tsc.is_signed_in.return_value = True class TimeoutArgTests(unittest.TestCase): @@ -392,7 +401,7 @@ class TimeoutIntegrationTest(unittest.TestCase): def test_connection_times_out(self): test_args = Namespace(**vars(args_to_mock)) new_session = Session() - test_args.timeout = 10 + test_args.timeout = 2 test_args.username = "u" test_args.password = "p" From 2063aa3a2ab31511f024224ea3861c858ad0642c Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Fri, 10 Mar 2023 02:18:50 -0800 Subject: [PATCH 4/9] add unit tests for setting new options haven't tested the use_certificate option properly yet, need a server that supports it --- tests/commands/test_session.py | 55 ++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/tests/commands/test_session.py b/tests/commands/test_session.py index d08a6aa3..874475da 100644 --- a/tests/commands/test_session.py +++ b/tests/commands/test_session.py @@ -412,36 +412,53 @@ def test_connection_times_out(self): # should test connection doesn't time out? -@mock.patch("tableauserverclient.Server") class ConnectionOptionsTest(unittest.TestCase): - def test_certcheck_on(self, mock_tsc): - mock_tsc.add_http_options = mock.MagicMock() - mock_session = mock.MagicMock() + + def test_user_agent(self): + mock_session = Session() + mock_session.server_url = "fakehost" + connection = mock_session._open_connection_with_opts() + assert connection._http_options["headers"]["User-Agent"].startswith("Tabcmd/") + + + def test_no_certcheck(self): + mock_session = Session() + mock_session.server_url = "fakehost" mock_session.no_certcheck = True mock_session.site_id = "s" mock_session.user_id = "u" - server = "anything" - mock_session._set_connection_options() - assert mock_tsc.add_http_options.has_been_called() + connection = mock_session._open_connection_with_opts() + assert connection._http_options["verify"] == False - def test_certcheck_off(self, mock_tsc): - mock_session = mock.MagicMock() - server = "anything" + def test_cert(self): + mock_session = Session() + mock_session.server_url = "fakehost" mock_session.site_id = "s" mock_session.user_id = "u" - mock_session._set_connection_options() - mock_tsc.add_http_options.assert_not_called() + mock_session.certificate = "my-cert-info" + connection = mock_session._open_connection_with_opts() + assert connection._http_options["cert"] == mock_session.certificate - """ - def test_cert(self, mock_tsc): - assert False, 'feature not implemented' + def test_proxy_stuff(self): + mock_session = Session() + mock_session.server_url = "fakehost" + mock_session.site_id = "s" + mock_session.user_id = "u" + mock_session.proxy = "proxy:port" + connection = mock_session._open_connection_with_opts() + assert connection._http_options["proxies"] == { "http": mock_session.proxy} - def test_proxy_stuff(self, mock_tsc): - assert False, 'feature not implemented' + def test_timeout(self): + mock_session = Session() + mock_session.server_url = "fakehost" + mock_session.site_id = "s" + mock_session.user_id = "u" + mock_session.timeout = 10 + connection = mock_session._open_connection_with_opts() + assert connection._http_options["timeout"] == 10 - def test_timeout(self, mock_tsc): - assert False, 'feature not implemented' +""" class CookieTests(unittest.TestCase): def test_no_file_if_no_cookie(self): From de16422b3c8bf244105d069f4787232a4eed6083 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Sat, 11 Mar 2023 14:33:36 -0800 Subject: [PATCH 5/9] Update session.py --- tabcmd/commands/auth/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tabcmd/commands/auth/session.py b/tabcmd/commands/auth/session.py index 91554874..16b845c7 100644 --- a/tabcmd/commands/auth/session.py +++ b/tabcmd/commands/auth/session.py @@ -74,7 +74,7 @@ def _update_session_data(self, args): self.token_value = args.token_value or self.token_value self.no_prompt = args.no_prompt # have to set this on every call? - self.certificate = args.use_certificate or self.certificate + self.certificate = args.certificate or self.certificate self.no_certcheck = args.no_certcheck # have to set this on every call? self.no_proxy = args.no_proxy # have to set this on every call? self.proxy = args.proxy or self.proxy From 933d27c361adae30a41c69b249255773b96ac873 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Sat, 11 Mar 2023 15:16:39 -0800 Subject: [PATCH 6/9] update error output on fatal crash --- tabcmd/__main__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tabcmd/__main__.py b/tabcmd/__main__.py index 50eace8f..5bd5fb1f 100644 --- a/tabcmd/__main__.py +++ b/tabcmd/__main__.py @@ -3,9 +3,8 @@ try: from tabcmd.tabcmd import main except ImportError as e: - print(sys.stderr, e) - print(sys.stderr, "Tabcmd needs to be run as a module, it cannot be run as a script") - print(sys.stderr, "Try running python -m tabcmd") + print("Exception thrown running program: `{}`, `{}`".format(e, e.__context__), file=sys.stderr) + print("[Possible cause: Tabcmd needs to be run as a module, try running `python -m tabcmd`]", file=sys.stderr) sys.exit(1) if __name__ == "__main__": From af662670d090477340b16abd117a6d48fd30d0b6 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Sat, 11 Mar 2023 15:23:40 -0800 Subject: [PATCH 7/9] tweak output --- tabcmd/commands/auth/session.py | 9 ++++----- tests/commands/test_session.py | 6 ++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/tabcmd/commands/auth/session.py b/tabcmd/commands/auth/session.py index 16b845c7..33e28627 100644 --- a/tabcmd/commands/auth/session.py +++ b/tabcmd/commands/auth/session.py @@ -213,7 +213,6 @@ def _verify_server_connection_unauthed(self): Errors.exit_with_error(self.logger, exception=e) def _create_new_connection(self) -> TSC.Server: - # self.logger.info(_("session.new_session")) self._print_server_info() self.logger.info(_("session.connecting")) try: @@ -230,10 +229,10 @@ def _print_server_info(self): self.logger.info("===== Server: {}".format(self.server_url)) if self.proxy: self.logger.info("===== Proxy: {}".format(self.proxy)) - if self.no_proxy: - self.logger.info("===== Proxy Override: set proxy to None") if self.username: self.logger.info("===== Username: {}".format(self.username)) + if self.certificate: + self.logger.info("===== Certificate: {}".format(self.proxy)) else: self.logger.info("===== Token Name: {}".format(self.token_name)) site_display_name = self.site_name or "Default Site" @@ -328,9 +327,9 @@ def create_session(self, args, logger): credentials = self._get_saved_credentials() if credentials and not signed_in_object: - self.logger.debug("We are not logged in yet but we have info to log in with") + self.logger.debug("We are not logged in yet but we have credentials to log in with") self.tableau_server = self._create_new_connection() - self._verify_server_connection_unauthed() # not sure if this line is necessary + self._verify_server_connection_unauthed() signed_in_object = self._sign_in(credentials) if not signed_in_object: diff --git a/tests/commands/test_session.py b/tests/commands/test_session.py index 874475da..dfecc3a3 100644 --- a/tests/commands/test_session.py +++ b/tests/commands/test_session.py @@ -18,7 +18,7 @@ no_certcheck=None, no_prompt=False, no_cookie=False, - use_certificate=None, + certificate=None, proxy=None, no_proxy=True, timeout=None, @@ -413,14 +413,12 @@ def test_connection_times_out(self): class ConnectionOptionsTest(unittest.TestCase): - def test_user_agent(self): mock_session = Session() mock_session.server_url = "fakehost" connection = mock_session._open_connection_with_opts() assert connection._http_options["headers"]["User-Agent"].startswith("Tabcmd/") - def test_no_certcheck(self): mock_session = Session() mock_session.server_url = "fakehost" @@ -446,7 +444,7 @@ def test_proxy_stuff(self): mock_session.user_id = "u" mock_session.proxy = "proxy:port" connection = mock_session._open_connection_with_opts() - assert connection._http_options["proxies"] == { "http": mock_session.proxy} + assert connection._http_options["proxies"] == {"http": mock_session.proxy} def test_timeout(self): mock_session = Session() From c9ebed16ad25379e3d76f23a643e275bda0b0e22 Mon Sep 17 00:00:00 2001 From: Jac Date: Thu, 23 Mar 2023 12:17:44 -0700 Subject: [PATCH 8/9] CR fixes to logs --- tabcmd/commands/auth/session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tabcmd/commands/auth/session.py b/tabcmd/commands/auth/session.py index 33e28627..87101791 100644 --- a/tabcmd/commands/auth/session.py +++ b/tabcmd/commands/auth/session.py @@ -188,7 +188,7 @@ def _open_connection_with_opts(self) -> TSC.Server: except Exception as e: self.logger.debug( "Connection args: server {}, site {}, proxy {}/no-proxy {}, cert {}".format( - self.server_url, self.site_name, self.proxy, self.certificate, self.no_proxy + self.server_url, self.site_name, self.proxy, self.no_proxy, self.certificate ) ) Errors.exit_with_error(self.logger, "Failed to connect to server", e) @@ -232,7 +232,7 @@ def _print_server_info(self): if self.username: self.logger.info("===== Username: {}".format(self.username)) if self.certificate: - self.logger.info("===== Certificate: {}".format(self.proxy)) + self.logger.info("===== Certificate: {}".format(self.certificate)) else: self.logger.info("===== Token Name: {}".format(self.token_name)) site_display_name = self.site_name or "Default Site" From eaf10e5e91bdec7dccb174696032af5ab727cb78 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Fri, 24 Mar 2023 13:49:51 -0700 Subject: [PATCH 9/9] specify black version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 17ac9ecf..a6e022b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,7 @@ dependencies = [ ] [project.optional-dependencies] test = [ - "black", + "black>=22,<23", "mock", "mypy", "pytest>=7.0",