diff --git a/.github/workflows/run-e2-tests.yml b/.github/workflows/run-e2-tests.yml index a3601d94..26f2222c 100644 --- a/.github/workflows/run-e2-tests.yml +++ b/.github/workflows/run-e2-tests.yml @@ -18,7 +18,7 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.7', '3.8', '3.9', '3.10', '3'] + python-version: ['3.9', '3.10', '3'] runs-on: ${{ matrix.os }} diff --git a/tabcmd/commands/auth/session.py b/tabcmd/commands/auth/session.py index ba98fd2d..866efbc8 100644 --- a/tabcmd/commands/auth/session.py +++ b/tabcmd/commands/auth/session.py @@ -4,7 +4,6 @@ import requests import tableauserverclient as TSC -import tableauserverclient.server.endpoint.exceptions from urllib3.exceptions import InsecureRequestWarning from tabcmd.commands.constants import Errors @@ -45,8 +44,8 @@ def __init__(self): self.timeout = None self.logging_level = "info" - self.logger = log(__class__.__name__, self.logging_level) self._read_from_json() + self.logger = log(__name__, self.logging_level) # instantiate here mostly for tests self.tableau_server = None # this one is an object that doesn't get persisted in the file # called before we connect to the server @@ -55,7 +54,6 @@ def _update_session_data(self, args): # user id and site id are never passed in as args # last_login_using and tableau_server are internal data # self.command = args.??? - # TODO: if server/username/token are changed, clear others self.username = args.username or self.username self.site_name = args.site_name or self.site_name or "" if self.site_name == "default": @@ -216,6 +214,7 @@ def create_session(self, args): self._read_existing_state() self._update_session_data(args) self.logging_level = args.logging_level or self.logging_level + self.logger = log(__name__, self.logging_level) credentials = None if args.password: diff --git a/tabcmd/commands/constants.py b/tabcmd/commands/constants.py index fe642bbb..0eaa9b3c 100644 --- a/tabcmd/commands/constants.py +++ b/tabcmd/commands/constants.py @@ -1,3 +1,4 @@ +import inspect import sys from tabcmd.execution.localize import _ @@ -29,9 +30,30 @@ def is_login_error(error): if hasattr(error, "code"): return error.code == Constants.login_error + # https://gist.github.com/FredLoney/5454553 + @staticmethod + def log_stack(logger): + try: + """The log header message formatter.""" + HEADER_FMT = "Printing Call Stack at %s::%s" + """The log stack message formatter.""" + STACK_FMT = "%s, line %d in function %s." + stack = inspect.stack() + here = stack[0] + file, line, func = here[1:4] + start = 0 + n_lines = 5 + logger.trace(HEADER_FMT % (file, func)) + for frame in stack[start + 1 : n_lines]: + file, line, func = frame[1:4] + logger.trace(STACK_FMT % (file, line, func)) + except BaseException as e: + logger.info("Error printing stack trace:", e) + @staticmethod def exit_with_error(logger, message=None, exception=None): try: + Errors.log_stack(logger) if message and not exception: logger.error(message) if exception: diff --git a/tabcmd/commands/datasources_and_workbooks/export_command.py b/tabcmd/commands/datasources_and_workbooks/export_command.py index 72f7375a..f05a88ce 100644 --- a/tabcmd/commands/datasources_and_workbooks/export_command.py +++ b/tabcmd/commands/datasources_and_workbooks/export_command.py @@ -38,25 +38,6 @@ def define_args(export_parser): help="View filter to apply to the view", ) - # TODO: ARGUMENT --COMPLETE - - @staticmethod - def get_content_url_for_workbook(url): - # check the size of list - separated_list = url.split("/") - reversed_list = separated_list[::-1] - return reversed_list[1] - - @staticmethod - def get_content_url_for_view(url): - # check the size of list - separated_list = url.split("/") - if len(separated_list) > 2: - print("error") - workbook_name = separated_list[0] - view_name = separated_list[1] - return DatasourcesAndWorkbooks.get_view_url_from_names(workbook_name, view_name) - """ Command to Export a view_name or workbook from Tableau Server and save it to a file. This command can also export just the data used for a view_name @@ -73,24 +54,27 @@ def run_command(args): if not view_content_url and not wb_content_url: Errors.exit_with_error(logger, _("export.errors.requires_workbook_view_param").format(ExportCommand)) - try: + logger.debug(args.pagelayout, args.pagesize, args.filename, args.width, args.height, args.filter) + try: if args.fullpdf: # it's a workbook workbook_item = ExportCommand.get_wb_by_content_url(logger, server, wb_content_url) - output = ExportCommand.download_wb_pdf(server, workbook_item) + output = ExportCommand.download_wb_pdf(server, workbook_item, args.url, logger) + default_filename = "{}.pdf".format(workbook_item.name) elif args.pdf or args.png or args.csv: # it's a view view_item = ExportCommand.get_view_by_content_url(logger, server, view_content_url) if args.pdf: - output = ExportCommand.download_view_pdf(server, view_item) + output = ExportCommand.download_view_pdf(server, view_item, args.url, logger) default_filename = "{}.pdf".format(view_item.name) elif args.csv: - output = ExportCommand.download_csv(server, view_item) + output = ExportCommand.download_csv(server, view_item, args.url, logger) default_filename = "{}.csv".format(view_item.name) elif args.png: - output = ExportCommand.download_png(server, view_item) + output = ExportCommand.download_png(server, view_item, args.url, logger) + default_filename = "{}.png".format(view_item.name) except Exception as e: @@ -98,32 +82,67 @@ def run_command(args): try: save_name = args.filename or default_filename - ExportCommand.save_to_file(logger, output, save_name) + if args.csv: + ExportCommand.save_to_data_file(logger, output, save_name) + else: + ExportCommand.save_to_file(logger, output, save_name) + except Exception as e: Errors.exit_with_error(logger, "Error saving to file", e) @staticmethod - def download_wb_pdf(server, workbook_item): + def extract_filter_values_from_url_params(request_options: TSC.PDFRequestOptions, url, logger=None) -> None: + try: + # todo make logging better + logger = logger or log(ExportCommand.__class__.__name__, "DEBUG") + logger.debug(url) + + if "?" in url: + query = url.split("?")[1] + else: + return + + params = query.split("&") + logger.trace(params) + for value in params: + data_filter = value.split("=") + request_options.vf(data_filter[0], data_filter[1]) + except BaseException as e: + logger.error("Error building filter params", e) + ExportCommand.log_stack(logger) # type: ignore + + @staticmethod + def download_wb_pdf(server, workbook_item, url, logger): + logger.trace(url) pdf = TSC.PDFRequestOptions(maxage=1) + ExportCommand.extract_filter_values_from_url_params(pdf, url) + server.workbooks.populate_pdf(workbook_item, pdf) return workbook_item.pdf @staticmethod - def download_view_pdf(server, view_item): + def download_view_pdf(server, view_item, url, logger): + logger.trace(url) pdf = TSC.PDFRequestOptions(maxage=1) + ExportCommand.extract_filter_values_from_url_params(pdf, url) + logger.trace(pdf.view_filters) server.views.populate_pdf(view_item, pdf) return view_item.pdf @staticmethod - def download_csv(server, view_item): + def download_csv(server, view_item, url, logger): + logger.trace(url) csv = TSC.CSVRequestOptions(maxage=1) + ExportCommand.extract_filter_values_from_url_params(csv, url) server.views.populate_csv(view_item, csv) return view_item.csv @staticmethod - def download_png(server, view_item): + def download_png(server, view_item, url, logger): + logger.trace(url) req_option_image = TSC.ImageRequestOptions(maxage=1) + ExportCommand.extract_filter_values_from_url_params(req_option_image, url) server.views.populate_image(view_item, req_option_image) return view_item.png @@ -132,7 +151,10 @@ def parse_export_url_to_workbook_and_view(logger, url): logger.info(_("export.status").format(url)) if " " in url: Errors.exit_with_error(logger, _("export.errors.white_space_workbook_view")) - # input should be workbook_name/view_name + if "?" in url: + url = url.split("?")[0] + # input should be workbook_name/view_name or /workbook_name/view_name + url = url.lstrip("/") # strip opening / if present if not url.find("/"): return None, None name_parts = url.split("/") @@ -142,9 +164,16 @@ def parse_export_url_to_workbook_and_view(logger, url): view = "{}/sheets/{}".format(workbook, name_parts[1]) return view, workbook + @staticmethod + def save_to_data_file(logger, output, filename): + logger.info(_("httputils.found_attachment").format(filename)) + with open(filename, "wb") as f: + f.writelines(output) + logger.info(_("export.success").format("", filename)) + @staticmethod def save_to_file(logger, output, filename): logger.info(_("httputils.found_attachment").format(filename)) with open(filename, "wb") as f: f.write(output) - logger.info(_("export.success").format(filename, "")) + logger.info(_("export.success").format("", filename)) diff --git a/tabcmd/commands/datasources_and_workbooks/get_url_command.py b/tabcmd/commands/datasources_and_workbooks/get_url_command.py index 5b23dc4e..4e1ba4b8 100644 --- a/tabcmd/commands/datasources_and_workbooks/get_url_command.py +++ b/tabcmd/commands/datasources_and_workbooks/get_url_command.py @@ -37,6 +37,10 @@ def run_command(args): if " " in args.url: Errors.exit_with_error(logger, _("export.errors.white_space_workbook_view")) + if not args.url.startswith("/"): + args.url = "/" + args.url + logger.trace("helpfully fix format of url: " + args.url) + file_type = GetUrl.get_file_type_from_filename(logger, args.filename, args.url) content_type = GetUrl.evaluate_content_type(logger, args.url) if content_type == "workbook": @@ -65,9 +69,12 @@ def evaluate_content_type(logger, url): elif url.find("/workbooks/") == 0: return "workbook" else: - Errors.exit_with_error( - logger, message=_("export.errors.requires_workbook_view_param").format(__class__.__name__) + view_example = "/views//." + message = "{} [{}]".format( + _("export.errors.requires_workbook_view_param").format(__class__.__name__), view_example ) + Errors.exit_with_error(logger, message) + @staticmethod def get_file_type_from_filename(logger, file_name, url): diff --git a/tabcmd/commands/group/delete_group_command.py b/tabcmd/commands/group/delete_group_command.py index 01a8a4b5..6d2741f1 100644 --- a/tabcmd/commands/group/delete_group_command.py +++ b/tabcmd/commands/group/delete_group_command.py @@ -27,7 +27,7 @@ def run_command(args): server = session.create_session(args) try: logger.info(_("tabcmd.find.group").format(args.name)) - group_id = Server.find_group_id(logger, server, args.name) + group_id = Server.find_group(logger, server, args.name).id logger.info(_("deletegroup.status").format(group_id)) server.groups.delete(group_id) logger.info(_("common.output.succeeded")) diff --git a/tabcmd/commands/server.py b/tabcmd/commands/server.py index 09fa965c..cf3700cf 100644 --- a/tabcmd/commands/server.py +++ b/tabcmd/commands/server.py @@ -39,18 +39,12 @@ def find_group(logger, server, group_name): Errors.exit_with_error(logger, exception=e) @staticmethod - def find_group_id(logger, server, group_name): - return Server.find_group(logger, server, group_name).id + def find_user(logger, server, username): + return Server.get_items_by_name(logger, server.users, username)[0] @staticmethod - def find_user_id(logger, server, username): - try: - return Server.get_items_by_name(logger, server.users, username)[0].id - except Exception as e: - Errors.exit_with_error(logger, exception=e) - - @staticmethod - def get_items_by_name(logger, item_endpoint, item_name: str, container: TSC.ProjectItem = None): + def get_items_by_name(logger, item_endpoint, item_name: str, container: TSC.ProjectItem = None) -> List: + # TODO: typing should reflect that this returns TSC.TableauItem and item_endpoint is of type TSC.QuerysetEndpoint[same] item_type = type(item_endpoint).__name__ item_log_name: str = "[" + item_type + "] " + item_name if container: @@ -58,10 +52,13 @@ def get_items_by_name(logger, item_endpoint, item_name: str, container: TSC.Proj logger.debug(_("export.status").format(item_log_name)) req_option = TSC.RequestOptions() req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, item_name)) - all_items, pagination_item = item_endpoint.get(req_option) if all_items is None or all_items == []: - raise ValueError("[" + item_type + "] " + _("errors.xmlapi.not_found")) + raise TSC.ServerResponseError( + code=404, + summary=_("errors.xmlapi.not_found"), + detail=_("errors.xmlapi.not_found") + ": " + item_log_name, + ) if len(all_items) == 1: logger.debug("Exactly one result found") result = all_items @@ -120,7 +117,7 @@ def get_project_by_name_and_parent_path(logger, server, project_name: str, paren if not parent_path: if not project_name: project_name = "Default" - project: TSC.ProjectItem = Server.get_items_by_name(logger, server.projects, project_name, None) + project: TSC.ProjectItem = Server.get_items_by_name(logger, server.projects, project_name, None)[0] return project project_tree: List[str] = Server._parse_project_path_to_list(parent_path) @@ -129,7 +126,9 @@ def get_project_by_name_and_parent_path(logger, server, project_name: str, paren return project parent = Server._get_parent_project_from_tree(logger, server, project_tree) + logger.debug(parent) project = Server._get_project_by_name_and_parent(logger, server, project_name, parent) + logger.debug(project) if not project: Errors.exit_with_error(logger, message=_("publish.errors.server_resource_not_found")) return project diff --git a/tabcmd/commands/user/delete_site_users_command.py b/tabcmd/commands/user/delete_site_users_command.py index 7f46a311..aa749a5a 100644 --- a/tabcmd/commands/user/delete_site_users_command.py +++ b/tabcmd/commands/user/delete_site_users_command.py @@ -42,7 +42,7 @@ def run_command(args): for user_obj in user_obj_list: logger.info(_("importcsvsummary.line.processed").format(number_of_users_deleted)) try: - user_id = UserCommand.find_user_id(logger, server, user_obj.name) + user_id = UserCommand.find_user(logger, server, user_obj.name).id server.users.remove(user_id) logger.debug(_("tabcmd.result.success.delete_user").format(user_obj.name, user_id)) number_of_users_deleted += 1 diff --git a/tabcmd/commands/user/user_data.py b/tabcmd/commands/user/user_data.py index ed7082c4..8e4caaeb 100644 --- a/tabcmd/commands/user/user_data.py +++ b/tabcmd/commands/user/user_data.py @@ -229,17 +229,25 @@ def act_on_users( logger.debug(_("importcsvsummary.line.processed").format(n_users_listed)) error_list = [] + line_no = 0 user_obj_list: List[TSC.UserItem] = UserCommand.get_users_from_file(args.users) logger.debug(_("tabcmd.result.success.parsed_users").format(len(user_obj_list))) for user_obj in user_obj_list: - username: str = user_obj.name or "unknown user" + line_no += 1 + if not user_obj.name: + number_of_errors += 1 + error_list.append(_("importcsvsummary.error.line").format(line_no, "No username", "")) + continue + try: - user_id: str = UserCommand.find_user_id(logger, server, username) + username: str = user_obj.name + user_id: str = UserCommand.find_user(logger, server, username).id logger.debug("{} user {} ({})".format(action_name, username, user_id)) except TSC.ServerResponseError as e: - Errors.check_common_error_codes_and_explain(logger, e) number_of_errors += 1 - error_list.append(e) + error_list.append( + _("importcsvsummary.error.line").format(line_no, username, "{}: {}".format(e.code, e.detail)) + ) logger.debug(_("tabcmd.result.failure.user").format(username)) continue @@ -248,11 +256,21 @@ def act_on_users( n_users_handled += 1 logger.info(_("tabcmd.result.success.user_actions").format(action_name, username, group)) except TSC.ServerResponseError as e: - Errors.check_common_error_codes_and_explain(logger, e) number_of_errors += 1 - error_list.append(e) + error_list.append( + _("importcsvsummary.error.line").format(line_no, username, "{}: {}".format(e.code, e.detail)) + ) logger.info(_("session.monitorjob.percent_complete").format(100)) logger.info(_("importcsvsummary.errors.count").format(number_of_errors)) if number_of_errors > 0: - logger.info(_("importcsvsummary.error.details").format(error_list)) + i = 0 + max_printing = 5 + logger.info(_("importcsvsummary.error.details")) + while i < number_of_errors and i < max_printing: + logger.info(error_list[i]) + i += 1 + if number_of_errors > max_printing: + logger.info(_("importcsvsummary.error.too_many_errors")) + logger.info(_("importcsvsummary.remainingerrors")) + diff --git a/tabcmd/execution/__init__.py b/tabcmd/execution/__init__.py index e69de29b..26cf34a5 100644 --- a/tabcmd/execution/__init__.py +++ b/tabcmd/execution/__init__.py @@ -0,0 +1,3 @@ +from tabcmd.execution.logger_config import add_trace_level + +add_trace_level() diff --git a/tabcmd/execution/logger_config.py b/tabcmd/execution/logger_config.py index 1837e951..c57de868 100644 --- a/tabcmd/execution/logger_config.py +++ b/tabcmd/execution/logger_config.py @@ -4,14 +4,43 @@ path = os.path.dirname(os.path.abspath(__file__)) FORMATS = { - logging.ERROR: "ERROR: %(name)-10s: %(lineno)d: %(message)s", - logging.WARN: "WARN: %(message)s", - logging.DEBUG: "DEBUG: %(name)-10s: %(lineno)d: %(message)-10s", - logging.INFO: "%(message)s", - "TRACE": "TRACE: %(asctime)-12s %(name)-10s: %(lineno)d: %(message)-10s", - "DEFAULT": "%(message)s", + logging.ERROR: "%(asctime)s %(levelname)-5s:(%(name)-10s %(filename)-10s: %(lineno)d): %(message)-30s", + logging.WARN: "%(asctime)s %(levelname)s : (%(name)-10s %(filename)-10s: %(lineno)d): %(message)-30s", + logging.INFO: "%(message)-30s", + logging.DEBUG: "%(asctime)s %(levelname)s : (%(name)-10s %(filename)-10s: %(lineno)d): %(message)-30s", } +# https://stackoverflow.com/questions/2183233/how-to-add-a-custom-loglevel-to-pythons-logging-facility +def add_log_level(level_name, level_num, method_name=None): + if not method_name: + method_name = level_name.lower() + + if hasattr(logging, level_name): + raise AttributeError("{} already defined in logging module".format(level_name)) + if hasattr(logging, method_name): + raise AttributeError("{} already defined in logging module".format(method_name)) + if hasattr(logging.getLoggerClass(), method_name): + raise AttributeError("{} already defined in logger class".format(method_name)) + + def logForLevel(self, message, *args, **kwargs): + if self.isEnabledFor(level_num): + self._log(level_num, message, args, **kwargs) + + def logToRoot(message, *args, **kwargs): + logging.log(level_num, message, *args, **kwargs) + + logging.addLevelName(level_num, level_name) + setattr(logging, level_name, level_num) + setattr(logging.getLoggerClass(), method_name, logForLevel) + setattr(logging, method_name, logToRoot) + + +def add_trace_level(): + trace_level: int = logging.DEBUG - 5 + add_log_level("TRACE", trace_level) + FORMATS[trace_level] = FORMATS[logging.ERROR] + + def configure_log(name: str, logging_level_input: str): """function for logging statements to console and logfile""" @@ -29,4 +58,6 @@ def configure_log(name: str, logging_level_input: str): def log(file_name, logging_level): logger = configure_log(file_name, logging_level) + if not hasattr(logger, "trace"): + logger.trace = logger.debug return logger diff --git a/tabcmd/execution/parent_parser.py b/tabcmd/execution/parent_parser.py index 82261f28..cb1a68d0 100644 --- a/tabcmd/execution/parent_parser.py +++ b/tabcmd/execution/parent_parser.py @@ -65,7 +65,7 @@ def parent_parser_with_global_options(self): parser.add_argument( "-l", "--logging-level", - choices=["DEBUG", "INFO", "ERROR"], + choices=["TRACE", "DEBUG", "INFO", "ERROR"], type=str.upper, # coerce input to uppercase to act case insensitive default="info", help="Use the specified logging level. The default level is INFO.", diff --git a/tabcmd/execution/tabcmd_controller.py b/tabcmd/execution/tabcmd_controller.py index f54f6b02..0dee0ad0 100644 --- a/tabcmd/execution/tabcmd_controller.py +++ b/tabcmd/execution/tabcmd_controller.py @@ -25,9 +25,14 @@ def run(parser, user_input=None): sys.exit(0) user_input = user_input or sys.argv[1:] namespace = parser.parse_args(user_input) + if namespace.logging_level: + print("logging:", namespace.logging_level) logger = log(__name__, namespace.logging_level or logging.INFO) - # logger.debug(namespace) + if namespace.password: + logger.trace(namespace.func) + else: + logger.trace(namespace) if namespace.language: set_client_locale(namespace.language, logger) diff --git a/tests/commands/test_geturl_utils.py b/tests/commands/test_geturl_utils.py index 2430bf28..0bdb4555 100644 --- a/tests/commands/test_geturl_utils.py +++ b/tests/commands/test_geturl_utils.py @@ -1,11 +1,19 @@ import unittest +from typing import Iterator from unittest import mock + +import tableauserverclient + from tabcmd.commands.datasources_and_workbooks.get_url_command import * from tabcmd.commands.datasources_and_workbooks.export_command import * from tabcmd.commands.server import Server mock_logger = mock.MagicMock() +fake_item = mock.MagicMock(TSC.ViewItem) +fake_item.name = "fake-name" +fake_item.id = "fake-id" + class GeturlTests(unittest.TestCase): def test_evaluate_file_name_pdf(self): @@ -59,9 +67,79 @@ def test_view_name(self): """ +@mock.patch("tableauserverclient.ViewItem", fake_item) class ExportTests(unittest.TestCase): - def test_parse_export_url_to_workbook(self): + + mock_logger = mock.MagicMock("logger") + fake_item.csv = mock.MagicMock("bytes[]") + fake_item.pdf = mock.MagicMock("bytes") + fake_item.png = mock.MagicMock("bytes") + + def test_parse_export_url_to_workbook_and_view(self): wb_url = "wb-name/view-name" view, wb = ExportCommand.parse_export_url_to_workbook_and_view(mock_logger, wb_url) assert view == "wb-name/sheets/view-name" assert wb == "wb-name" + + def test_parse_export_url_to_workbook_and_view_with_start_slash(self): + wb_url = "/wb-name/view-name" + view, wb = ExportCommand.parse_export_url_to_workbook_and_view(mock_logger, wb_url) + assert view == "wb-name/sheets/view-name" + assert wb == "wb-name" + + def test_parse_export_url_to_workbook_and_view_bad_url(self): + wb_url = "wb-name/view-name/kitty" + view, wb = ExportCommand.parse_export_url_to_workbook_and_view(mock_logger, wb_url) + assert view is None + assert wb is None + + def test_extract_query_params(self): + url = "wb-name/view-name?param1=value1" + options = TSC.PDFRequestOptions() + assert options.view_filters is not None + assert len(options.view_filters) is 0 + ExportCommand.extract_filter_values_from_url_params(options, url) + assert len(options.view_filters) == 1 + assert options.view_filters[0] == ("param1", "value1") + + @mock.patch("tableauserverclient.Server") + def test_download_csv(self, mock_server): + mock_server.views = mock.MagicMock() + mock_server.views.csv = mock.MagicMock() + mock_view = tableauserverclient.ViewItem() + url = "wb-name/view-name?param1=value1" + ExportCommand.download_csv(mock_server, mock_view, url, mock_logger) + + @mock.patch("tableauserverclient.Server") + def test_download_image(self, mock_server): + mock_server.views = mock.MagicMock() + mock_server.views.png = mock.MagicMock() + mock_view = tableauserverclient.ViewItem() + url = "wb-name/view-name?param1=value1" + ExportCommand.download_png(mock_server, mock_view, url, mock_logger) + + @mock.patch("tableauserverclient.Server") + def test_download_view_pdf(self, mock_server): + mock_server.views = mock.MagicMock() + mock_server.views.pdf = mock.MagicMock() + mock_view = tableauserverclient.ViewItem() + url = "wb-name/view-name?param1=value1" + ExportCommand.download_view_pdf(mock_server, mock_view, url, mock_logger) + + @mock.patch("tableauserverclient.Server") + def test_download_wb_pdf(self, mock_server): + mock_server.workbooks = mock.MagicMock() + mock_server.workbooks.pdf = mock.MagicMock() + mock_view = tableauserverclient.ViewItem() + url = "wb-name/view-name?param1=value1" + ExportCommand.download_wb_pdf(mock_server, mock_view, url, mock_logger) + + def test_save_to_binary_file(self): + mock_content = bytes() + filename = "test_out.pdf" + ExportCommand.save_to_file(mock_logger, mock_content, filename) + + def test_save_to_data_file(self): + mock_content = mock.MagicMock() + filename = "test_out.csv" + ExportCommand.save_to_data_file(mock_logger, mock_content, filename) diff --git a/tests/commands/test_run_commands.py b/tests/commands/test_run_commands.py index 7e7e3d45..22b9dda0 100644 --- a/tests/commands/test_run_commands.py +++ b/tests/commands/test_run_commands.py @@ -103,6 +103,15 @@ def test_export(self, mock_session, mock_server): mock_args.fullpdf = True mock_args.filename = "filename.pdf" mock_args.url = "workbook-name/view-name" + mock_args.csv = None + mock_args.image = None + mock_args.pdf = None + mock_args.pagelayout = None + mock_args.pagesize = None + mock_args.size = None + mock_args.height = None + mock_args.width = None + mock_args.filter = None export_command.ExportCommand.run_command(mock_args) mock_session.assert_called() diff --git a/tests/e2e/online_tests.py b/tests/e2e/online_tests.py index cb391e29..bce557ef 100644 --- a/tests/e2e/online_tests.py +++ b/tests/e2e/online_tests.py @@ -53,8 +53,7 @@ def _create_project(self, project_name, parent_path=None): arguments = [command, "--name", project_name] if parent_path or parent_location: arguments.append("--parent-project-path") - arguments.append(parent_path or parent_location) - print(arguments) + arguments.append(parent_path) _test_command(arguments) def _delete_project(self, project_name, parent_path=None): @@ -136,7 +135,7 @@ def test_login(self): @pytest.mark.order(1) def test_version(self): - command = "" + command = "-v" arguments = [command] _test_command(arguments) @@ -147,7 +146,7 @@ def test_help(self): _test_command(arguments) @pytest.mark.order(2) - def test_create_site_users(self): + def test_users_create_site_users(self): if not server_admin and not site_admin: pytest.skip("Must be server or site administrator to create site users") command = "createsiteusers" @@ -156,7 +155,7 @@ def test_create_site_users(self): _test_command(arguments) @pytest.mark.order(3) - def test_creategroup(self): + def test_group_creategroup(self): if not server_admin and not site_admin: pytest.skip("Must be server or site administrator to create groups") groupname = group_name @@ -165,7 +164,7 @@ def test_creategroup(self): _test_command(arguments) @pytest.mark.order(4) - def test_add_users_to_group(self): + def test_users_add_to_group(self): if not server_admin and not site_admin: pytest.skip("Must be server or site administrator to add to groups") @@ -176,7 +175,7 @@ def test_add_users_to_group(self): _test_command(arguments) @pytest.mark.order(5) - def test_remove_users_to_group(self): + def test_users_remove_from_group(self): if not server_admin and not site_admin: pytest.skip("Must be server or site administrator to remove from groups") @@ -187,7 +186,7 @@ def test_remove_users_to_group(self): _test_command(arguments) @pytest.mark.order(6) - def test_deletegroup(self): + def test_group_deletegroup(self): if not server_admin and not site_admin: pytest.skip("Must be server or site administrator to delete groups") @@ -198,9 +197,12 @@ def test_deletegroup(self): @pytest.mark.order(8) def test_create_projects(self): - if not project_admin: pytest.skip("Must be project administrator to create projects") + + # project 1 + self._create_project(parent_location) + time.sleep(indexing_sleep_time) # project 1 self._create_project(project_name) time.sleep(indexing_sleep_time) @@ -216,58 +218,54 @@ def test_create_projects(self): def test_list_projects(self): self._list("projects") - @pytest.mark.order(9) + """ + @pytest.mark.order(9) + def test_publish_samples(self): + self._publish_samples(project_name) + """ + + @pytest.mark.order(10) def test_delete_projects(self): if not project_admin: pytest.skip("Must be project administrator to create projects") self._delete_project("project_name_2", project_name) # project 2 self._delete_project(project_name) - @pytest.mark.order(9) - def test_publish_samples(self): - self._publish_samples(project_name) - @pytest.mark.order(10) - def test_publish(self): + def test_wb_publish(self): name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME file = os.path.join("tests", "assets", OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) self._publish_wb(file, name_on_server) @pytest.mark.order(10) - def test__get_wb(self): - wb_name_on_server = OnlineCommandTest.TWBX_WITHOUT_EXTRACT_NAME - self._get_workbook(wb_name_on_server + ".twbx") + def test_wb_get(self): + self._get_workbook(OnlineCommandTest.TWBX_WITH_EXTRACT_NAME + ".twbx") @pytest.mark.order(10) - def test__get_wb(self): + def test_view_get_pdf(self): wb_name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME - self._get_workbook(wb_name_on_server + ".twbx") - - @pytest.mark.order(10) - def test__get_view(self): - wb_name_on_server = OnlineCommandTest.TWBX_WITHOUT_EXTRACT_NAME - sheet_name = OnlineCommandTest.TWBX_WITHOUT_EXTRACT_SHEET + sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET self._get_view(wb_name_on_server, sheet_name + ".pdf") @pytest.mark.order(10) - def test__get_view_csv(self): + def test_view_get_csv(self): wb_name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET self._get_view(wb_name_on_server, sheet_name + ".csv") @pytest.mark.order(10) - def test__get_view_png(self): + def test_view_get_png(self): wb_name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET self._get_view(wb_name_on_server, sheet_name + ".png") @pytest.mark.order(11) - def test__delete_wb(self): + def test_wb_delete(self): name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME self._delete_wb(name_on_server) @pytest.mark.order(12) - def test_delete_extract(self): + def test_extract_delete(self): # fails because the extract has a bad data connection :/ name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME file = os.path.join("tests", "assets", OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) @@ -275,30 +273,24 @@ def test_delete_extract(self): self._delete_extract(name_on_server) @pytest.mark.order(13) - def test_create_extract(self): + def test_extract_create(self): # Fails because it 'already has an extract' :/ name_on_server = OnlineCommandTest.TWBX_WITHOUT_EXTRACT_NAME self._create_extract(name_on_server) @pytest.mark.order(14) - def test_refresh_extract(self): + def test_extract_refresh(self): # must be a datasource owned by the test user + name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME + file = os.path.join("tests", "assets", OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) + self._publish_wb(file, name_on_server) + name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME self._refresh_extract(name_on_server) self._delete_wb(name_on_server) - def test_version(self): - _test_command(["-v"]) - - def test_help(self): - _test_command(["help"]) - - # this just gets in the way :( - # def test_logout(self): - # _test_command(["logout"]) - @pytest.mark.order(15) - def test_export_wb(self): + def test_export_wb_pdf(self): name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME file = os.path.join("tests", "assets", OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) self._publish_wb(file, name_on_server) @@ -308,17 +300,17 @@ def test_export_wb(self): _test_command(arguments) @pytest.mark.order(15) - def test_export_view(self): + def test_export_view_pdf(self): name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME file = os.path.join("tests", "assets", OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) self._publish_wb(file, name_on_server) command = "export" - friendly_name = name_on_server + "/" + OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET + friendly_name = name_on_server + "/" + OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET + "?param1=3" arguments = [command, friendly_name, "--pdf", "-f", "exported_view.pdf"] _test_command(arguments) @pytest.mark.order(16) - def test_delete_site_users(self): + def test_users_delete_site_users(self): if not server_admin and not site_admin: pytest.skip("Must be server or site administrator to delete site users")