From 1903586249862afbd05c1494841cd8cd04934ddc Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Thu, 14 Aug 2025 15:32:08 -0700 Subject: [PATCH 1/9] test cleanliness - change test asset names - make a more reliable way to get unique publishing names for reliability - fix option for -d in extract refresh command --- ...rld Indicators.tds => WorldIndicators.tds} | 0 ...d Indicators.tdsx => WorldIndicators.tdsx} | Bin tests/assets/live_mysql.tds | 878 ++++++++++++++++++ tests/e2e/online_tests.py | 155 ++-- 4 files changed, 976 insertions(+), 57 deletions(-) rename tests/assets/{World Indicators.tds => WorldIndicators.tds} (100%) rename tests/assets/{World Indicators.tdsx => WorldIndicators.tdsx} (100%) create mode 100644 tests/assets/live_mysql.tds diff --git a/tests/assets/World Indicators.tds b/tests/assets/WorldIndicators.tds similarity index 100% rename from tests/assets/World Indicators.tds rename to tests/assets/WorldIndicators.tds diff --git a/tests/assets/World Indicators.tdsx b/tests/assets/WorldIndicators.tdsx similarity index 100% rename from tests/assets/World Indicators.tdsx rename to tests/assets/WorldIndicators.tdsx diff --git a/tests/assets/live_mysql.tds b/tests/assets/live_mysql.tds new file mode 100644 index 00000000..83bbec56 --- /dev/null +++ b/tests/assets/live_mysql.tds @@ -0,0 +1,878 @@ + + + + + + + + + + + + + + Item Count + 3 + [Item Count] + [staples] + Item Count + 1 + integer + Sum + 10 + false + + "SQL_INTEGER" + "SQL_C_SLONG" + + + + Ship Priority + 129 + [Ship Priority] + [staples] + Ship Priority + 2 + string + Count + 14 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Order Priority + 129 + [Order Priority] + [staples] + Order Priority + 3 + string + Count + 15 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Order Status + 129 + [Order Status] + [staples] + Order Status + 4 + string + Count + 13 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Order Quantity + 5 + [Order Quantity] + [staples] + Order Quantity + 5 + real + Sum + 15 + false + + "SQL_DOUBLE" + "SQL_C_DOUBLE" + + + + Sales Total + 5 + [Sales Total] + [staples] + Sales Total + 6 + real + Sum + 15 + false + + "SQL_DOUBLE" + "SQL_C_DOUBLE" + + + + Discount + 5 + [Discount] + [staples] + Discount + 7 + real + Sum + 15 + false + + "SQL_DOUBLE" + "SQL_C_DOUBLE" + + + + Tax Rate + 5 + [Tax Rate] + [staples] + Tax Rate + 8 + real + Sum + 15 + false + + "SQL_DOUBLE" + "SQL_C_DOUBLE" + + + + Ship Mode + 129 + [Ship Mode] + [staples] + Ship Mode + 9 + string + Count + 25 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Fill Time + 5 + [Fill Time] + [staples] + Fill Time + 10 + real + Sum + 15 + false + + "SQL_DOUBLE" + "SQL_C_DOUBLE" + + + + Gross Profit + 5 + [Gross Profit] + [staples] + Gross Profit + 11 + real + Sum + 15 + false + + "SQL_DOUBLE" + "SQL_C_DOUBLE" + + + + Price + 131 + [Price] + [staples] + Price + 12 + real + Sum + 18 + 4 + false + + "SQL_DECIMAL" + "SQL_C_NUMERIC" + + + + Ship Handle Cost + 131 + [Ship Handle Cost] + [staples] + Ship Handle Cost + 13 + real + Sum + 18 + 4 + false + + "SQL_DECIMAL" + "SQL_C_NUMERIC" + + + + Employee Name + 129 + [Employee Name] + [staples] + Employee Name + 14 + string + Count + 50 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Employee Dept + 129 + [Employee Dept] + [staples] + Employee Dept + 15 + string + Count + 4 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Manager Name + 129 + [Manager Name] + [staples] + Manager Name + 16 + string + Count + 255 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Employee Yrs Exp + 5 + [Employee Yrs Exp] + [staples] + Employee Yrs Exp + 17 + real + Sum + 15 + false + + "SQL_DOUBLE" + "SQL_C_DOUBLE" + + + + Employee Salary + 131 + [Employee Salary] + [staples] + Employee Salary + 18 + real + Sum + 18 + 4 + false + + "SQL_DECIMAL" + "SQL_C_NUMERIC" + + + + Customer Name + 129 + [Customer Name] + [staples] + Customer Name + 19 + string + Count + 50 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Customer State + 129 + [Customer State] + [staples] + Customer State + 20 + string + Count + 50 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Call Center Region + 129 + [Call Center Region] + [staples] + Call Center Region + 21 + string + Count + 25 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Customer Balance + 5 + [Customer Balance] + [staples] + Customer Balance + 22 + real + Sum + 15 + false + + "SQL_DOUBLE" + "SQL_C_DOUBLE" + + + + Customer Segment + 129 + [Customer Segment] + [staples] + Customer Segment + 23 + string + Count + 25 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Prod Type1 + 129 + [Prod Type1] + [staples] + Prod Type1 + 24 + string + Count + 50 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Prod Type2 + 129 + [Prod Type2] + [staples] + Prod Type2 + 25 + string + Count + 50 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Prod Type3 + 129 + [Prod Type3] + [staples] + Prod Type3 + 26 + string + Count + 50 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Prod Type4 + 129 + [Prod Type4] + [staples] + Prod Type4 + 27 + string + Count + 50 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Product Name + 129 + [Product Name] + [staples] + Product Name + 28 + string + Count + 100 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Product Container + 129 + [Product Container] + [staples] + Product Container + 29 + string + Count + 25 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Ship Promo + 129 + [Ship Promo] + [staples] + Ship Promo + 30 + string + Count + 25 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Supplier Name + 129 + [Supplier Name] + [staples] + Supplier Name + 31 + string + Count + 25 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Supplier Balance + 5 + [Supplier Balance] + [staples] + Supplier Balance + 32 + real + Sum + 15 + false + + "SQL_DOUBLE" + "SQL_C_DOUBLE" + + + + Supplier Region + 129 + [Supplier Region] + [staples] + Supplier Region + 33 + string + Count + 25 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Supplier State + 129 + [Supplier State] + [staples] + Supplier State + 34 + string + Count + 50 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Order ID + 129 + [Order ID] + [staples] + Order ID + 35 + string + Count + 10 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Order Year + 3 + [Order Year] + [staples] + Order Year + 36 + integer + Sum + 10 + false + + "SQL_INTEGER" + "SQL_C_SLONG" + + + + Order Month + 3 + [Order Month] + [staples] + Order Month + 37 + integer + Sum + 10 + false + + "SQL_INTEGER" + "SQL_C_SLONG" + + + + Order Day + 3 + [Order Day] + [staples] + Order Day + 38 + integer + Sum + 10 + false + + "SQL_INTEGER" + "SQL_C_SLONG" + + + + Order Date + 7 + [Order Date] + [staples] + Order Date + 39 + datetime + Year + false + + "SQL_TYPE_TIMESTAMP" + "SQL_C_TYPE_TIMESTAMP" + + + + Order Quarter + 129 + [Order Quarter] + [staples] + Order Quarter + 40 + string + Count + 2 + false + true + + + "SQL_CHAR" + "SQL_C_CHAR" + + + + Product Base Margin + 5 + [Product Base Margin] + [staples] + Product Base Margin + 41 + real + Sum + 15 + false + + "SQL_DOUBLE" + "SQL_C_DOUBLE" + + + + Product ID + 129 + [Product ID] + [staples] + Product ID + 42 + string + Count + 5 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + Receive Time + 5 + [Receive Time] + [staples] + Receive Time + 43 + real + Sum + 15 + false + + "SQL_DOUBLE" + "SQL_C_DOUBLE" + + + + Received Date + 7 + [Received Date] + [staples] + Received Date + 44 + datetime + Year + false + + "SQL_TYPE_TIMESTAMP" + "SQL_C_TYPE_TIMESTAMP" + + + + Ship Date + 7 + [Ship Date] + [staples] + Ship Date + 45 + datetime + Year + false + + "SQL_TYPE_TIMESTAMP" + "SQL_C_TYPE_TIMESTAMP" + + + + Ship Charge + 131 + [Ship Charge] + [staples] + Ship Charge + 46 + real + Sum + 18 + 4 + false + + "SQL_DECIMAL" + "SQL_C_NUMERIC" + + + + Total Cycle Time + 5 + [Total Cycle Time] + [staples] + Total Cycle Time + 47 + real + Sum + 15 + false + + "SQL_DOUBLE" + "SQL_C_DOUBLE" + + + + Product In Stock + 129 + [Product In Stock] + [staples] + Product In Stock + 48 + string + Count + 3 + false + true + + + "SQL_CHAR" + "SQL_C_CHAR" + + + + PID + 3 + [PID] + [staples] + PID + 49 + integer + Sum + 10 + false + + "SQL_INTEGER" + "SQL_C_SLONG" + + + + Market Segment + 129 + [Market Segment] + [staples] + Market Segment + 50 + string + Count + 25 + false + + + "SQL_VARCHAR" + "SQL_C_CHAR" + "true" + + + + + + + + + + + + + + + diff --git a/tests/e2e/online_tests.py b/tests/e2e/online_tests.py index 1e1bc9ca..988ae9e4 100644 --- a/tests/e2e/online_tests.py +++ b/tests/e2e/online_tests.py @@ -28,7 +28,7 @@ unique = str(time.gmtime().tm_sec) group_name = "test-ing-group" + unique workbook_name = "wb_1_" + unique -default_project_name = "Personal Work" # "default-proj" + unique +default_project_name = "Personal Work" # has to exist already when you run random test cases "default-proj" + parent_location = "parent" + unique project_name = "test-proj-" + unique @@ -177,17 +177,17 @@ def _get_datasource(self, server_file): arguments = [command, server_file] _test_command(arguments) - def _create_extract(self, type, wb_name): + def _create_extract(self, item_name, type="-w"): command = "createextracts" - arguments = [command, type, wb_name, "--project", default_project_name] + arguments = [command, type, item_name, "--project", default_project_name] if extract_encryption_enabled and not use_tabcmd_classic: arguments.append("--encrypt") _test_command(arguments) # variation: url - def _refresh_extract(self, type, wb_name): + def _refresh_extract(self, item_name, type="-w"): command = "refreshextracts" - arguments = [command, "-w", wb_name, "--project", default_project_name] # bug: should not need -w + arguments = [command, type, item_name, "--project", default_project_name] # bug: should not need -w try: _test_command(arguments) except Exception as e: @@ -199,7 +199,7 @@ def _refresh_extract(self, type, wb_name): else: raise e - def _delete_extract(self, type, item_name): + def _delete_extract(self, item_name, type="-w"): command = "deleteextracts" arguments = [command, type, item_name, "--include-all", "--project", default_project_name] try: @@ -219,21 +219,44 @@ def _list(self, item_type: str): arguments = [command, item_type] _test_command(arguments) - # actual tests + def get_publishable_name(self, file_value: str) -> str: + return os.path.splitext(os.path.basename(file_value))[0]+unique + + # assets for tests + # if we publish something with a name that already exists, it will get a random int appended to the name + # to avoid this, we add our own random int to each name so we actually know what it is + # this is why we have workbook_name+unique everywhere TWBX_FILE_WITH_EXTRACT = "WorkbookWithExtract.twbx" - TWBX_WITH_EXTRACT_NAME = "WorkbookWithExtract" TWBX_WITH_EXTRACT_SHEET = "Sheet1" + TWBX_FILE_WITHOUT_EXTRACT = "simple-data.twbx" - TWBX_WITHOUT_EXTRACT_NAME = "WorkbookWithoutExtract" TWBX_WITHOUT_EXTRACT_SHEET = "Testsheet1" - TDSX_WITH_EXTRACT_NAME = "WorldIndicators" - TDSX_FILE_WITH_EXTRACT = "World Indicators.tdsx" - # fill in - TDS_FILE_LIVE_NAME = "SampleDS" - TDS_FILE_LIVE = "SampleDS.tds" - TWB_WITH_EMBEDDED_CONNECTION = "EmbeddedCredentials.twb" - EMBEDDED_TWB_NAME = "EmbeddedCredentials" + # problem: 803311: Remove Extract is not supported for this Datasources (errorCode=310030)) + TDSX_FILE_WITH_EXTRACT = "WorldIndicators.tdsx" + # WorldIndicators.tds + + TDS_FILE_LIVE = "live_mysql.tds" + + TWB_FILE_WITH_EMBEDDED_CONNECTION = "EmbeddedCredentials.twb" + + # check for the required files in the test assets + @pytest.mark.order(0) + def test_asset_files_exist(self): + assets_dir = os.path.join("tests", "assets") + checks = [ + ("TWBX_FILE_WITH_EXTRACT", OnlineCommandTest.TWBX_FILE_WITH_EXTRACT), + ("TWBX_FILE_WITHOUT_EXTRACT", OnlineCommandTest.TWBX_FILE_WITHOUT_EXTRACT), + ("TDSX_FILE_WITH_EXTRACT", OnlineCommandTest.TDSX_FILE_WITH_EXTRACT), + ("TDS_FILE_LIVE", OnlineCommandTest.TDS_FILE_LIVE), + ("TWB_FILE_WITH_EMBEDDED_CONNECTION", OnlineCommandTest.TWB_FILE_WITH_EMBEDDED_CONNECTION), + ] + missing = [] + for var_name, filename in checks: + path = os.path.join(assets_dir, filename) + if not os.path.exists(path): + missing.append(f"{var_name} -> {path}") + assert not missing, "Missing asset files: " + ", ".join(missing) @pytest.mark.order(1) def test_login(self): @@ -252,6 +275,13 @@ def test_help(self): arguments = [command] _test_command(arguments) + @pytest.mark.order(1) + def test_publish_simple(self): + file = os.path.join("tests", "assets", OnlineCommandTest.TWBX_FILE_WITHOUT_EXTRACT) + name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITHOUT_EXTRACT) + arguments = self._publish_args(file, name_on_server) + _test_command(arguments) + @pytest.mark.order(2) def test_users_create_site_users(self): if not server_admin and not site_admin: @@ -359,28 +389,31 @@ def test_delete_projects(self): @pytest.mark.order(10) def test_wb_publish(self): - file = os.path.join("tests", "assets", OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) - arguments = self._publish_args(file, OnlineCommandTest.TWBX_WITH_EXTRACT_NAME) - val = _test_command(arguments) - if val != 0: - print("publishing failed: cancel test run") - exit(val) + for file in [OnlineCommandTest.TWBX_FILE_WITH_EXTRACT, OnlineCommandTest.TWBX_FILE_WITHOUT_EXTRACT]: + file = os.path.join("tests", "assets", file) + name_on_server = self.get_publishable_name(file) + arguments = self._publish_args(file, name_on_server) + val = _test_command(arguments) + if val != 0: + print(f"publishing {file} failed: cancel test run") + exit(val) @pytest.mark.order(11) def test_wb_get(self): # add .twbx to the end to tell the server what we are getting - self._get_workbook(OnlineCommandTest.TWBX_WITH_EXTRACT_NAME + ".twbx") + name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT)+".twbx" + self._get_workbook(name_on_server) @pytest.mark.order(11) def test_view_get_pdf(self): - wb_name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME + wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET # bug in tabcmd classic: doesn't work without download name self._get_view(wb_name_on_server, sheet_name, "downloaded_file.pdf") @pytest.mark.order(11) def test_view_get_png_sizes(self): - wb_name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME + wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET self._get_view(wb_name_on_server, sheet_name, "get_view_default_size.png") @@ -391,20 +424,21 @@ def test_view_get_png_sizes(self): @pytest.mark.order(11) def test_view_get_csv(self): - wb_name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME + wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET self._get_view(wb_name_on_server, sheet_name + ".csv") @pytest.mark.order(11) def test_view_get_png(self): - wb_name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME + wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET self._get_view(wb_name_on_server, sheet_name + ".png") @pytest.mark.order(11) def test_wb_publish_embedded(self): - file = os.path.join("tests", "assets", OnlineCommandTest.TWB_WITH_EMBEDDED_CONNECTION) - arguments = self._publish_args(file, OnlineCommandTest.EMBEDDED_TWB_NAME) + file = os.path.join("tests", "assets", OnlineCommandTest.TWB_FILE_WITH_EMBEDDED_CONNECTION) + name_on_server = self.get_publishable_name(OnlineCommandTest.TWB_FILE_WITH_EMBEDDED_CONNECTION) + arguments = self._publish_args(file, name_on_server) arguments = self._publish_creds_args(arguments, database_user, database_password, True) arguments.append("--tabbed") arguments.append("--skip-connection-check") @@ -413,53 +447,45 @@ def test_wb_publish_embedded(self): @pytest.mark.order(12) def test_publish_ds(self): file = os.path.join("tests", "assets", OnlineCommandTest.TDSX_FILE_WITH_EXTRACT) - arguments = self._publish_args(file, OnlineCommandTest.TDSX_WITH_EXTRACT_NAME) + name_on_server = self.get_publishable_name(OnlineCommandTest.TDSX_FILE_WITH_EXTRACT) + arguments = self._publish_args(file, name_on_server) _test_command(arguments) @pytest.mark.order(12) def test_publish_live_ds(self): file = os.path.join("tests", "assets", OnlineCommandTest.TDS_FILE_LIVE) - arguments = self._publish_args(file, OnlineCommandTest.TDS_FILE_LIVE_NAME) + name_on_server = self.get_publishable_name(OnlineCommandTest.TDS_FILE_LIVE) + arguments = self._publish_args(file, name_on_server) _test_command(arguments) @pytest.mark.order(13) def test__get_ds(self): - self._get_datasource(OnlineCommandTest.TDSX_WITH_EXTRACT_NAME + ".tdsx") + name_on_server = self.get_publishable_name(OnlineCommandTest.TDSX_FILE_WITH_EXTRACT) + self._get_datasource(name_on_server + ".tdsx") @pytest.mark.order(13) def test_refresh_ds_extract(self): - self._refresh_extract("-d", OnlineCommandTest.TDSX_WITH_EXTRACT_NAME) + name_on_server = self.get_publishable_name(OnlineCommandTest.TDSX_FILE_WITH_EXTRACT) + self._refresh_extract(name_on_server, "-d") @pytest.mark.order(14) def test_delete_extract(self): - self._delete_extract("-d", OnlineCommandTest.TDSX_WITH_EXTRACT_NAME) + name_on_server = self.get_publishable_name(OnlineCommandTest.TDSX_FILE_WITH_EXTRACT) + self._delete_extract(name_on_server, "-d") @pytest.mark.order(16) def test_create_extract(self): - self._create_extract("-d", OnlineCommandTest.TDS_FILE_LIVE_NAME) + name_on_server = self.get_publishable_name(OnlineCommandTest.TDS_FILE_LIVE) + self._create_extract(name_on_server, "-d") @pytest.mark.order(17) def test_refresh_wb_extract(self): - self._refresh_extract("-w", OnlineCommandTest.TWBX_WITH_EXTRACT_NAME) - - @pytest.mark.order(19) - def test_wb_delete(self): - name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME - self._delete_wb(name_on_server) - - @pytest.mark.order(19) - def test__delete_ds(self): - name_on_server = OnlineCommandTest.TDSX_WITH_EXTRACT_NAME - self._delete_ds(name_on_server) - - @pytest.mark.order(19) - def test__delete_ds_live(self): - name_on_server = OnlineCommandTest.TDS_FILE_LIVE_NAME - self._delete_ds(name_on_server) + name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) + self._refresh_extract(name_on_server) @pytest.mark.order(19) def test_export_wb_filters(self): - wb_name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME + wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET friendly_name = wb_name_on_server + "/" + sheet_name filters = ["--filter", "Product Type=Tea", "--fullpdf", "--pagelayout", "landscape"] @@ -468,32 +494,32 @@ def test_export_wb_filters(self): @pytest.mark.order(19) def test_export_wb_pdf(self): - wb_name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME + wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) friendly_name = wb_name_on_server + "/" + OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET filename = "exported_wb.pdf" self._export_wb(friendly_name, filename) @pytest.mark.order(19) def test_export_data_csv(self): - wb_name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME + wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET self._export_view(wb_name_on_server, sheet_name, "--csv", "exported_data.csv") @pytest.mark.order(19) def test_export_view_png(self): - wb_name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME + wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET self._export_view(wb_name_on_server, sheet_name, "--png", "export_view.png") @pytest.mark.order(19) def test_export_view_pdf(self): - wb_name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME + wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET self._export_view(wb_name_on_server, sheet_name, "--pdf", "export_view_pdf.pdf") @pytest.mark.order(19) def test_export_view_filtered(self): - wb_name_on_server = OnlineCommandTest.TWBX_WITH_EXTRACT_NAME + wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET filename = "view_with_filters.pdf" @@ -521,3 +547,18 @@ def test_list_sites(self): except Exception as E: result = False assert result + + @pytest.mark.order(30) + def test_wb_delete(self): + name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) + self._delete_wb(name_on_server) + + @pytest.mark.order(30) + def test__delete_ds(self): + name_on_server = self.get_publishable_name(OnlineCommandTest.TDSX_FILE_WITH_EXTRACT) + self._delete_ds(name_on_server) + + @pytest.mark.order(30) + def test__delete_ds_live(self): + name_on_server = self.get_publishable_name(OnlineCommandTest.TDS_FILE_LIVE) + self._delete_ds(name_on_server) From 515257ff7298f4284d7d2d57400c97dfffb58a5a Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Tue, 7 Oct 2025 01:25:45 -0700 Subject: [PATCH 2/9] confirm required tests/assests --- WorldIndicators.tdsx | Bin 0 -> 63815 bytes tests/assets/simple-data.twbx | Bin 19615 -> 0 bytes tests/e2e/online_tests.py | 24 +++++++++++++++--------- 3 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 WorldIndicators.tdsx delete mode 100644 tests/assets/simple-data.twbx diff --git a/WorldIndicators.tdsx b/WorldIndicators.tdsx new file mode 100644 index 0000000000000000000000000000000000000000..4e4e08513064b77f98efdb7302dee839859d0fb0 GIT binary patch literal 63815 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 UsKOZO80k<4&KCx=gLTvW2M;DxH2?qr diff --git a/tests/e2e/online_tests.py b/tests/e2e/online_tests.py index 988ae9e4..159c80e8 100644 --- a/tests/e2e/online_tests.py +++ b/tests/e2e/online_tests.py @@ -21,6 +21,9 @@ # you can either run setup with a stored credentials file, or simply log in # before running the suite so a session is active +# TEST BUG: these fail if the uploaded content gets a different name (like wb_1) because it already exists on that site +# AGH which it does even if the wb with that name is deleted from the site + debug_log = "--logging-level=DEBUG" indexing_sleep_time = 1 # wait 1 second to confirm server has indexed updates @@ -219,13 +222,14 @@ def _list(self, item_type: str): arguments = [command, item_type] _test_command(arguments) - def get_publishable_name(self, file_value: str) -> str: - return os.path.splitext(os.path.basename(file_value))[0]+unique - # assets for tests # if we publish something with a name that already exists, it will get a random int appended to the name # to avoid this, we add our own random int to each name so we actually know what it is # this is why we have workbook_name+unique everywhere + def get_publishable_name(self, file_value: str) -> str: + return os.path.splitext(os.path.basename(file_value))[0]+unique + + # assets for tests - these files are kept in the repo in tests/assets TWBX_FILE_WITH_EXTRACT = "WorkbookWithExtract.twbx" TWBX_WITH_EXTRACT_SHEET = "Sheet1" @@ -258,6 +262,9 @@ def test_asset_files_exist(self): missing.append(f"{var_name} -> {path}") assert not missing, "Missing asset files: " + ", ".join(missing) + USERS_DETAILS_FILE = "detailed_users.csv" + USERNAMES_FILE = "usernames.csv" + @pytest.mark.order(1) def test_login(self): if use_tabcmd_classic: @@ -287,7 +294,7 @@ 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" - users = os.path.join("tests", "assets", "detailed_users.csv") + users = os.path.join("tests", "assets", OnlineCommandTest.USERS_DETAILS_FILE) arguments = [command, users, "--role", "Publisher"] _test_command(arguments) @@ -309,7 +316,7 @@ def test_users_add_to_group(self): groupname = group_name command = "addusers" - filename = os.path.join("tests", "assets", "usernames.csv") + filename = os.path.join("tests", "assets", OnlineCommandTest.USERNAMES_FILE) arguments = [command, groupname, "--users", filename] if not use_tabcmd_classic: arguments.append("--continue-if-exists") @@ -322,7 +329,7 @@ def test_users_remove_from_group(self): groupname = group_name command = "removeusers" - filename = os.path.join("tests", "assets", "usernames.csv") + filename = os.path.join("tests", "assets", OnlineCommandTest.USERNAMES_FILE) arguments = [command, groupname, "--users", filename] _test_command(arguments) @@ -480,8 +487,7 @@ def test_create_extract(self): @pytest.mark.order(17) def test_refresh_wb_extract(self): - name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) - self._refresh_extract(name_on_server) + self._refresh_extract("-w", OnlineCommandTest.TWBX_WITH_EXTRACT_NAME) @pytest.mark.order(19) def test_export_wb_filters(self): @@ -532,7 +538,7 @@ def test_delete_site_users(self): pytest.skip("Must be server or site administrator to delete site users") command = "deletesiteusers" - users = os.path.join("tests", "assets", "usernames.csv") + users = os.path.join("tests", "assets", OnlineCommandTest.USERNAMES_FILE) _test_command([command, users]) @pytest.mark.order(21) From 366ee004e37ab027f36f8b7393e3d9fccc0fe00c Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Tue, 7 Oct 2025 22:13:10 -0700 Subject: [PATCH 3/9] update online_tests Make tests use new unique names each time, so that re-runs pass. --- tests/e2e/online_tests.py | 309 ++++++++++++++++----------------- tests/e2e/tests_integration.py | 2 + 2 files changed, 152 insertions(+), 159 deletions(-) diff --git a/tests/e2e/online_tests.py b/tests/e2e/online_tests.py index 159c80e8..f60216f4 100644 --- a/tests/e2e/online_tests.py +++ b/tests/e2e/online_tests.py @@ -21,20 +21,9 @@ # you can either run setup with a stored credentials file, or simply log in # before running the suite so a session is active -# TEST BUG: these fail if the uploaded content gets a different name (like wb_1) because it already exists on that site -# AGH which it does even if the wb with that name is deleted from the site - +# config variables for test run debug_log = "--logging-level=DEBUG" indexing_sleep_time = 1 # wait 1 second to confirm server has indexed updates - -# object names: helpful if they are always unique -unique = str(time.gmtime().tm_sec) -group_name = "test-ing-group" + unique -workbook_name = "wb_1_" + unique -default_project_name = "Personal Work" # has to exist already when you run random test cases "default-proj" + -parent_location = "parent" + unique -project_name = "test-proj-" + unique - # Flags to let us skip tests if we know we don't have the required access server_admin = False site_admin = True @@ -43,6 +32,41 @@ use_tabcmd_classic = False # toggle between testing using tabcmd 2 or tabcmd classic +default_project_name = "Personal Work" # not unique, has to exist already when you run random test cases + +class TestAssets: + + unique = str(time.gmtime().tm_sec) + + # names unique for each test run + group_name = "test-ing-group" + unique + parent_location = "parent" + unique + + # if we publish something with a name that already exists, it will get a random int appended to the name + # to avoid this, we add our own random int to each name so we actually know what it is + # this is why we have workbook_name+unique everywhere + # BUG: this means you pretty much can't run a random individual test case without giving the already-unique name + def get_publishable_name(file_value: str) -> str: + return os.path.splitext(os.path.basename(file_value))[0]+TestAssets.unique + + # assets for tests - these files are kept in the repo in tests/assets + TWBX_FILE_WITH_EXTRACT = "WorkbookWithExtract.twbx" + TWBX_WITH_EXTRACT_SHEET = "Sheet1" + + TWBX_FILE_WITHOUT_EXTRACT = "WorkbookWithoutExtract.twbx" + TWBX_WITHOUT_EXTRACT_SHEET = "Testsheet1" + + # problem: 803311: Remove Extract is not supported for this Datasources (errorCode=310030)) + TDSX_FILE_WITH_EXTRACT = "WorldIndicators.tdsx" + # WorldIndicators.tds + + TDS_FILE_LIVE = "live_mysql.tds" + + TWB_FILE_WITH_EMBEDDED_CONNECTION = "EmbeddedCredentials.twb" + + USERS_DETAILS_FILE = "detailed_users.csv" + USERNAMES_FILE = "usernames.csv" + def _test_command(test_args: list[str]): # this will raise an exception if it gets a non-zero return code # that will bubble up and fail the test @@ -63,19 +87,11 @@ def _test_command(test_args: list[str]): print(calling_args) return subprocess.check_call(calling_args) - -class OnlineCommandTest(unittest.TestCase): - published = False - gotten = False - - @classmethod - def setup_class(cls): - print("running python -m") - # call this if we are using the built exe setup_e2e.prechecks() +class TabcmdCall: # Individual methods that implement a command - def _create_project(self, project_name, parent_path=None): + def _create_project(project_name, parent_path=None): command = "createproject" arguments = [command, "--name", project_name] if parent_path: @@ -85,7 +101,7 @@ def _create_project(self, project_name, parent_path=None): arguments.append("--continue-if-exists") _test_command(arguments) - def _delete_project(self, project_name, parent_path=None): + def _delete_project(project_name, parent_path=None): command = "deleteproject" arguments = [command, project_name] if parent_path: @@ -93,12 +109,12 @@ def _delete_project(self, project_name, parent_path=None): arguments.append(parent_path) _test_command(arguments) - def _publish_samples(self, project_name): + def _publish_samples(project_name): command = "publishsamples" arguments = [command, "--name", project_name] _test_command(arguments) - def _publish_args(self, file, name, optional_args=None): + def _publish_args(file, name, optional_args=None): command = "publish" arguments = [command, file, "--name", name, "--project", default_project_name, "--overwrite"] if optional_args: @@ -106,7 +122,7 @@ def _publish_args(self, file, name, optional_args=None): return arguments def _publish_creds_args( - self, arguments, db_user=None, db_pass=None, db_save=None, oauth_user=None, oauth_save=None + arguments, db_user=None, db_pass=None, db_save=None, oauth_user=None, oauth_save=None ): if db_user: arguments.append("--db-username") @@ -123,17 +139,17 @@ def _publish_creds_args( arguments.append("--save-oauth") return arguments - def _delete_wb(self, name): + def _delete_wb(name): command = "delete" arguments = [command, "--project", default_project_name, name] _test_command(arguments) - def _delete_ds(self, name): + def _delete_ds(name): command = "delete" arguments = [command, "--project", default_project_name, "--datasource", name] _test_command(arguments) - def _get_view(self, wb_name_on_server, sheet_name, filename=None, additional_args=None): + def _get_view(wb_name_on_server, sheet_name, filename=None, additional_args=None): server_file = "/views/" + wb_name_on_server + "/" + sheet_name command = "get" arguments = [command, server_file] @@ -146,8 +162,9 @@ def _get_view(self, wb_name_on_server, sheet_name, filename=None, additional_arg def _get_custom_view(self): # TODO command = "get" + raise NotImplementedError("get_custom_view is not implemented") - def _export_wb(self, friendly_name, filename=None, additional_args=None): + def _export_wb(friendly_name, filename=None, additional_args=None): command = "export" arguments = [command, friendly_name, "--fullpdf"] @@ -157,7 +174,7 @@ def _export_wb(self, friendly_name, filename=None, additional_args=None): arguments = arguments + additional_args _test_command(arguments) - def _export_view(self, wb_name_on_server, sheet_name, export_type, filename=None, additional_args=None): + def _export_view(wb_name_on_server, sheet_name, export_type, filename=None, additional_args=None): server_file = "/" + wb_name_on_server + "/" + sheet_name command = "export" arguments = [command, server_file, export_type] @@ -167,20 +184,20 @@ def _export_view(self, wb_name_on_server, sheet_name, export_type, filename=None arguments = arguments + additional_args _test_command(arguments) - def _get_workbook(self, server_file): + def _get_workbook(server_file): command = "get" server_file = "/workbooks/" + server_file arguments = [command, server_file, "-f", "get_workbook.twbx"] _test_command(arguments) os.path.exists("get_workbook.twbx") - def _get_datasource(self, server_file): + def _get_datasource(server_file): command = "get" server_file = "/datasources/" + server_file arguments = [command, server_file] _test_command(arguments) - def _create_extract(self, item_name, type="-w"): + def _create_extract(item_name, type="-w"): command = "createextracts" arguments = [command, type, item_name, "--project", default_project_name] if extract_encryption_enabled and not use_tabcmd_classic: @@ -188,7 +205,7 @@ def _create_extract(self, item_name, type="-w"): _test_command(arguments) # variation: url - def _refresh_extract(self, item_name, type="-w"): + def _refresh_extract(item_name, type="-w"): command = "refreshextracts" arguments = [command, type, item_name, "--project", default_project_name] # bug: should not need -w try: @@ -202,58 +219,34 @@ def _refresh_extract(self, item_name, type="-w"): else: raise e - def _delete_extract(self, item_name, type="-w"): + def _delete_extract(item_name, type="-w"): command = "deleteextracts" arguments = [command, type, item_name, "--include-all", "--project", default_project_name] - try: - _test_command(arguments) - except Exception as e: - print(e) - if use_tabcmd_classic: - print("Expected (tabcmd classic):") - print("*** Unexpected response from the server: Unable to load Data Source") - print("Remove extract operation failed. (errorCode=310028)") - print("8530479: Remove Extract is not supported for this Datasources (errorCode=310030)") - else: - raise e + _test_command(arguments) - def _list(self, item_type: str): + def _list(item_type: str): command = "list" arguments = [command, item_type] _test_command(arguments) +# test cases that use the API calls +class OnlineCommandTest(unittest.TestCase): - # if we publish something with a name that already exists, it will get a random int appended to the name - # to avoid this, we add our own random int to each name so we actually know what it is - # this is why we have workbook_name+unique everywhere - def get_publishable_name(self, file_value: str) -> str: - return os.path.splitext(os.path.basename(file_value))[0]+unique - - # assets for tests - these files are kept in the repo in tests/assets - TWBX_FILE_WITH_EXTRACT = "WorkbookWithExtract.twbx" - TWBX_WITH_EXTRACT_SHEET = "Sheet1" - - TWBX_FILE_WITHOUT_EXTRACT = "simple-data.twbx" - TWBX_WITHOUT_EXTRACT_SHEET = "Testsheet1" - - # problem: 803311: Remove Extract is not supported for this Datasources (errorCode=310030)) - TDSX_FILE_WITH_EXTRACT = "WorldIndicators.tdsx" - # WorldIndicators.tds - - TDS_FILE_LIVE = "live_mysql.tds" - - TWB_FILE_WITH_EMBEDDED_CONNECTION = "EmbeddedCredentials.twb" + @classmethod + def setup_class(cls): + print("running python -m") + # call this if we are using the built exe setup_e2e.prechecks() # check for the required files in the test assets @pytest.mark.order(0) def test_asset_files_exist(self): assets_dir = os.path.join("tests", "assets") checks = [ - ("TWBX_FILE_WITH_EXTRACT", OnlineCommandTest.TWBX_FILE_WITH_EXTRACT), - ("TWBX_FILE_WITHOUT_EXTRACT", OnlineCommandTest.TWBX_FILE_WITHOUT_EXTRACT), - ("TDSX_FILE_WITH_EXTRACT", OnlineCommandTest.TDSX_FILE_WITH_EXTRACT), - ("TDS_FILE_LIVE", OnlineCommandTest.TDS_FILE_LIVE), - ("TWB_FILE_WITH_EMBEDDED_CONNECTION", OnlineCommandTest.TWB_FILE_WITH_EMBEDDED_CONNECTION), + ("TWBX_FILE_WITH_EXTRACT", TestAssets.TWBX_FILE_WITH_EXTRACT), + ("TWBX_FILE_WITHOUT_EXTRACT", TestAssets.TWBX_FILE_WITHOUT_EXTRACT), + ("TDSX_FILE_WITH_EXTRACT", TestAssets.TDSX_FILE_WITH_EXTRACT), + ("TDS_FILE_LIVE", TestAssets.TDS_FILE_LIVE), + ("TWB_FILE_WITH_EMBEDDED_CONNECTION", TestAssets.TWB_FILE_WITH_EMBEDDED_CONNECTION), ] missing = [] for var_name, filename in checks: @@ -262,9 +255,6 @@ def test_asset_files_exist(self): missing.append(f"{var_name} -> {path}") assert not missing, "Missing asset files: " + ", ".join(missing) - USERS_DETAILS_FILE = "detailed_users.csv" - USERNAMES_FILE = "usernames.csv" - @pytest.mark.order(1) def test_login(self): if use_tabcmd_classic: @@ -284,9 +274,9 @@ def test_help(self): @pytest.mark.order(1) def test_publish_simple(self): - file = os.path.join("tests", "assets", OnlineCommandTest.TWBX_FILE_WITHOUT_EXTRACT) - name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITHOUT_EXTRACT) - arguments = self._publish_args(file, name_on_server) + file = os.path.join("tests", "assets", TestAssets.TWBX_FILE_WITHOUT_EXTRACT) + name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITHOUT_EXTRACT) + arguments = TabcmdCall._publish_args(file, name_on_server) _test_command(arguments) @pytest.mark.order(2) @@ -294,7 +284,7 @@ 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" - users = os.path.join("tests", "assets", OnlineCommandTest.USERS_DETAILS_FILE) + users = os.path.join("tests", "assets", TestAssets.USERS_DETAILS_FILE) arguments = [command, users, "--role", "Publisher"] _test_command(arguments) @@ -302,7 +292,7 @@ def test_users_create_site_users(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 + groupname = TestAssets.group_name command = "creategroup" arguments = [command, groupname] if not use_tabcmd_classic: @@ -314,9 +304,9 @@ 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") - groupname = group_name + groupname = TestAssets.group_name command = "addusers" - filename = os.path.join("tests", "assets", OnlineCommandTest.USERNAMES_FILE) + filename = os.path.join("tests", "assets", TestAssets.USERNAMES_FILE) arguments = [command, groupname, "--users", filename] if not use_tabcmd_classic: arguments.append("--continue-if-exists") @@ -327,9 +317,9 @@ 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") - groupname = group_name + groupname = TestAssets.group_name command = "removeusers" - filename = os.path.join("tests", "assets", OnlineCommandTest.USERNAMES_FILE) + filename = os.path.join("tests", "assets", TestAssets.USERNAMES_FILE) arguments = [command, groupname, "--users", filename] _test_command(arguments) @@ -338,7 +328,7 @@ def test_group_deletegroup(self): if not server_admin and not site_admin: pytest.skip("Must be server or site administrator to delete groups") - groupname = group_name + groupname = TestAssets.group_name command = "deletegroup" arguments = [command, groupname] _test_command(arguments) @@ -349,57 +339,57 @@ def test_create_projects(self): pytest.skip("Must be project administrator to create projects") # project 1 - self._create_project(parent_location) + TabcmdCall._create_project(TestAssets.parent_location) time.sleep(indexing_sleep_time) # project 1 - self._create_project(default_project_name) + TabcmdCall._create_project(default_project_name) time.sleep(indexing_sleep_time) # project 2 - self._create_project("project_name_2", default_project_name) + TabcmdCall._create_project("project_name_2", default_project_name) time.sleep(indexing_sleep_time) # project 3 parent_path = "{0}/{1}".format(default_project_name, "project_name_2") - self._create_project(default_project_name, parent_path) + TabcmdCall._create_project(default_project_name, parent_path) time.sleep(indexing_sleep_time) @pytest.mark.order(8) def test_list_projects(self): if use_tabcmd_classic: pytest.skip("not for tabcmd classic") - self._list("projects") + TabcmdCall._list("projects") @pytest.mark.order(8) def test_list_flows(self): if use_tabcmd_classic: pytest.skip("not for tabcmd classic") - self._list("flows") + TabcmdCall._list("flows") @pytest.mark.order(8) def test_list_workbooks(self): if use_tabcmd_classic: pytest.skip("not for tabcmd classic") - self._list("workbooks") + TabcmdCall._list("workbooks") @pytest.mark.order(8) def test_list_datasources(self): if use_tabcmd_classic: pytest.skip("not for tabcmd classic") - self._list("datasources") + TabcmdCall._list("datasources") @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(parent_location) - self._delete_project("project_name_2", default_project_name) # project 2 + TabcmdCall._delete_project(TestAssets.parent_location) + TabcmdCall._delete_project("project_name_2", default_project_name) # project 2 self._delete_project(default_project_name) @pytest.mark.order(10) def test_wb_publish(self): - for file in [OnlineCommandTest.TWBX_FILE_WITH_EXTRACT, OnlineCommandTest.TWBX_FILE_WITHOUT_EXTRACT]: + for file in [TestAssets.TWBX_FILE_WITH_EXTRACT, TestAssets.TWBX_FILE_WITHOUT_EXTRACT]: file = os.path.join("tests", "assets", file) - name_on_server = self.get_publishable_name(file) - arguments = self._publish_args(file, name_on_server) + name_on_server = TestAssets.get_publishable_name(file) + arguments = TabcmdCall._publish_args(file, name_on_server) val = _test_command(arguments) if val != 0: print(f"publishing {file} failed: cancel test run") @@ -408,129 +398,130 @@ def test_wb_publish(self): @pytest.mark.order(11) def test_wb_get(self): # add .twbx to the end to tell the server what we are getting - name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT)+".twbx" - self._get_workbook(name_on_server) + name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT)+".twbx" + TabcmdCall._get_workbook(name_on_server) @pytest.mark.order(11) def test_view_get_pdf(self): - wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) - sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET + wb_name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT) + sheet_name = TestAssets.TWBX_WITH_EXTRACT_SHEET # bug in tabcmd classic: doesn't work without download name - self._get_view(wb_name_on_server, sheet_name, "downloaded_file.pdf") + TabcmdCall._get_view(wb_name_on_server, sheet_name, "downloaded_file.pdf") @pytest.mark.order(11) def test_view_get_png_sizes(self): - wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) - sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET + wb_name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT) + sheet_name = TestAssets.TWBX_WITH_EXTRACT_SHEET - self._get_view(wb_name_on_server, sheet_name, "get_view_default_size.png") + TabcmdCall._get_view(wb_name_on_server, sheet_name, "get_view_default_size.png") url_params = "?:size=100,200" - self._get_view(wb_name_on_server, sheet_name + url_params, "get_view_sized_sm.png") + TabcmdCall._get_view(wb_name_on_server, sheet_name + url_params, "get_view_sized_sm.png") url_params = "?:size=500,700" - self._get_view(wb_name_on_server, sheet_name + url_params, "get_view_sized_LARGE.png") + TabcmdCall._get_view(wb_name_on_server, sheet_name + url_params, "get_view_sized_LARGE.png") @pytest.mark.order(11) def test_view_get_csv(self): - wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) - sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET - self._get_view(wb_name_on_server, sheet_name + ".csv") + wb_name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT) + sheet_name = TestAssets.TWBX_WITH_EXTRACT_SHEET + TabcmdCall._get_view(wb_name_on_server, sheet_name + ".csv") @pytest.mark.order(11) def test_view_get_png(self): - wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) - sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET - self._get_view(wb_name_on_server, sheet_name + ".png") + wb_name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT) + sheet_name = TestAssets.TWBX_WITH_EXTRACT_SHEET + TabcmdCall._get_view(wb_name_on_server, sheet_name + ".png") @pytest.mark.order(11) def test_wb_publish_embedded(self): - file = os.path.join("tests", "assets", OnlineCommandTest.TWB_FILE_WITH_EMBEDDED_CONNECTION) - name_on_server = self.get_publishable_name(OnlineCommandTest.TWB_FILE_WITH_EMBEDDED_CONNECTION) - arguments = self._publish_args(file, name_on_server) - arguments = self._publish_creds_args(arguments, database_user, database_password, True) + file = os.path.join("tests", "assets", TestAssets.TWB_FILE_WITH_EMBEDDED_CONNECTION) + name_on_server = TestAssets.get_publishable_name(TestAssets.TWB_FILE_WITH_EMBEDDED_CONNECTION) + arguments = TabcmdCall._publish_args(file, name_on_server) + arguments = TabcmdCall._publish_creds_args(arguments, database_user, database_password, True) arguments.append("--tabbed") arguments.append("--skip-connection-check") _test_command(arguments) @pytest.mark.order(12) def test_publish_ds(self): - file = os.path.join("tests", "assets", OnlineCommandTest.TDSX_FILE_WITH_EXTRACT) - name_on_server = self.get_publishable_name(OnlineCommandTest.TDSX_FILE_WITH_EXTRACT) - arguments = self._publish_args(file, name_on_server) + file = os.path.join("tests", "assets", TestAssets.TDSX_FILE_WITH_EXTRACT) + name_on_server = TestAssets.get_publishable_name(TestAssets.TDSX_FILE_WITH_EXTRACT) + arguments = TabcmdCall._publish_args(file, name_on_server) _test_command(arguments) @pytest.mark.order(12) def test_publish_live_ds(self): - file = os.path.join("tests", "assets", OnlineCommandTest.TDS_FILE_LIVE) - name_on_server = self.get_publishable_name(OnlineCommandTest.TDS_FILE_LIVE) - arguments = self._publish_args(file, name_on_server) + file = os.path.join("tests", "assets", TestAssets.TDS_FILE_LIVE) + name_on_server = TestAssets.get_publishable_name(TestAssets.TDS_FILE_LIVE) + arguments = TabcmdCall._publish_args(file, name_on_server) _test_command(arguments) @pytest.mark.order(13) def test__get_ds(self): - name_on_server = self.get_publishable_name(OnlineCommandTest.TDSX_FILE_WITH_EXTRACT) - self._get_datasource(name_on_server + ".tdsx") + name_on_server = TestAssets.get_publishable_name(TestAssets.TDSX_FILE_WITH_EXTRACT) + TabcmdCall._get_datasource(name_on_server + ".tdsx") @pytest.mark.order(13) def test_refresh_ds_extract(self): - name_on_server = self.get_publishable_name(OnlineCommandTest.TDSX_FILE_WITH_EXTRACT) - self._refresh_extract(name_on_server, "-d") + name_on_server = TestAssets.get_publishable_name(TestAssets.TDSX_FILE_WITH_EXTRACT) + TabcmdCall._refresh_extract(name_on_server, "-d") @pytest.mark.order(14) def test_delete_extract(self): - name_on_server = self.get_publishable_name(OnlineCommandTest.TDSX_FILE_WITH_EXTRACT) - self._delete_extract(name_on_server, "-d") + name_on_server = TestAssets.get_publishable_name(TestAssets.TDSX_FILE_WITH_EXTRACT) + TabcmdCall._delete_extract(name_on_server, "-d") @pytest.mark.order(16) def test_create_extract(self): - name_on_server = self.get_publishable_name(OnlineCommandTest.TDS_FILE_LIVE) - self._create_extract(name_on_server, "-d") + name_on_server = TestAssets.get_publishable_name(TestAssets.TDS_FILE_LIVE) + TabcmdCall._create_extract(name_on_server, "-d") @pytest.mark.order(17) def test_refresh_wb_extract(self): - self._refresh_extract("-w", OnlineCommandTest.TWBX_WITH_EXTRACT_NAME) + name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT) + TabcmdCall._refresh_extract(name_on_server, "-w") @pytest.mark.order(19) def test_export_wb_filters(self): - wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) - sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET + wb_name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT) + sheet_name = TestAssets.TWBX_WITH_EXTRACT_SHEET friendly_name = wb_name_on_server + "/" + sheet_name filters = ["--filter", "Product Type=Tea", "--fullpdf", "--pagelayout", "landscape"] - self._export_wb(friendly_name, "filter_a_wb_to_tea_and_two_pages.pdf", filters) + TabcmdCall._export_wb(friendly_name, "filter_a_wb_to_tea_and_two_pages.pdf", filters) # NOTE: this test needs a visual check on the returned pdf to confirm the expected appearance @pytest.mark.order(19) def test_export_wb_pdf(self): - wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) - friendly_name = wb_name_on_server + "/" + OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET + wb_name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT) + friendly_name = wb_name_on_server + "/" + TestAssets.TWBX_WITH_EXTRACT_SHEET filename = "exported_wb.pdf" - self._export_wb(friendly_name, filename) + TabcmdCall._export_wb(friendly_name, filename) @pytest.mark.order(19) def test_export_data_csv(self): - wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) - sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET - self._export_view(wb_name_on_server, sheet_name, "--csv", "exported_data.csv") + wb_name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT) + sheet_name = TestAssets.TWBX_WITH_EXTRACT_SHEET + TabcmdCall._export_view(wb_name_on_server, sheet_name, "--csv", "exported_data.csv") @pytest.mark.order(19) def test_export_view_png(self): - wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) - sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET - self._export_view(wb_name_on_server, sheet_name, "--png", "export_view.png") + wb_name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT) + sheet_name = TestAssets.TWBX_WITH_EXTRACT_SHEET + TabcmdCall._export_view(wb_name_on_server, sheet_name, "--png", "export_view.png") @pytest.mark.order(19) def test_export_view_pdf(self): - wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) - sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET - self._export_view(wb_name_on_server, sheet_name, "--pdf", "export_view_pdf.pdf") + wb_name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT) + sheet_name = TestAssets.TWBX_WITH_EXTRACT_SHEET + TabcmdCall._export_view(wb_name_on_server, sheet_name, "--pdf", "export_view_pdf.pdf") @pytest.mark.order(19) def test_export_view_filtered(self): - wb_name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) - sheet_name = OnlineCommandTest.TWBX_WITH_EXTRACT_SHEET + wb_name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT) + sheet_name = TestAssets.TWBX_WITH_EXTRACT_SHEET filename = "view_with_filters.pdf" filters = ["--filter", "Product Type=Tea"] - self._export_view(wb_name_on_server, sheet_name, "--pdf", filename, filters) + TabcmdCall._export_view(wb_name_on_server, sheet_name, "--pdf", filename, filters) @pytest.mark.order(20) def test_delete_site_users(self): @@ -538,7 +529,7 @@ def test_delete_site_users(self): pytest.skip("Must be server or site administrator to delete site users") command = "deletesiteusers" - users = os.path.join("tests", "assets", OnlineCommandTest.USERNAMES_FILE) + users = os.path.join("tests", "assets", TestAssets.USERNAMES_FILE) _test_command([command, users]) @pytest.mark.order(21) @@ -556,15 +547,15 @@ def test_list_sites(self): @pytest.mark.order(30) def test_wb_delete(self): - name_on_server = self.get_publishable_name(OnlineCommandTest.TWBX_FILE_WITH_EXTRACT) - self._delete_wb(name_on_server) + name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT) + TabcmdCall._delete_wb(name_on_server) @pytest.mark.order(30) def test__delete_ds(self): - name_on_server = self.get_publishable_name(OnlineCommandTest.TDSX_FILE_WITH_EXTRACT) - self._delete_ds(name_on_server) + name_on_server = TestAssets.get_publishable_name(TestAssets.TDSX_FILE_WITH_EXTRACT) + TabcmdCall._delete_ds(name_on_server) @pytest.mark.order(30) def test__delete_ds_live(self): - name_on_server = self.get_publishable_name(OnlineCommandTest.TDS_FILE_LIVE) - self._delete_ds(name_on_server) + name_on_server = TestAssets.get_publishable_name(TestAssets.TDS_FILE_LIVE) + TabcmdCall._delete_ds(name_on_server) diff --git a/tests/e2e/tests_integration.py b/tests/e2e/tests_integration.py index f63588b1..935751ae 100644 --- a/tests/e2e/tests_integration.py +++ b/tests/e2e/tests_integration.py @@ -65,6 +65,7 @@ def test_log_in(): no_cookie=False, ) test_session = Session() + logger = log(__class__.__name__, "info") server = test_session.create_session(args, logger) assert test_session.auth_token is not None assert test_session.site_id is not None @@ -95,6 +96,7 @@ def test_reuse_session(self): timeout=None, no_cookie=False, ) + logger = log(__class__.__name__, "info") test_session = Session() test_session.create_session(args, logger) assert test_session.auth_token is not None From 0d4fd0782f6648932924154f08f6cf0ac9c34f05 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Tue, 7 Oct 2025 22:29:46 -0700 Subject: [PATCH 4/9] black --- tests/e2e/online_tests.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/e2e/online_tests.py b/tests/e2e/online_tests.py index f60216f4..5a0c34ba 100644 --- a/tests/e2e/online_tests.py +++ b/tests/e2e/online_tests.py @@ -32,7 +32,8 @@ use_tabcmd_classic = False # toggle between testing using tabcmd 2 or tabcmd classic -default_project_name = "Personal Work" # not unique, has to exist already when you run random test cases +default_project_name = "Personal Work" # not unique, has to exist already when you run random test cases + class TestAssets: @@ -41,13 +42,13 @@ class TestAssets: # names unique for each test run group_name = "test-ing-group" + unique parent_location = "parent" + unique - + # if we publish something with a name that already exists, it will get a random int appended to the name - # to avoid this, we add our own random int to each name so we actually know what it is + # to avoid this, we add our own random int to each name so we actually know what it is # this is why we have workbook_name+unique everywhere # BUG: this means you pretty much can't run a random individual test case without giving the already-unique name def get_publishable_name(file_value: str) -> str: - return os.path.splitext(os.path.basename(file_value))[0]+TestAssets.unique + return os.path.splitext(os.path.basename(file_value))[0] + TestAssets.unique # assets for tests - these files are kept in the repo in tests/assets TWBX_FILE_WITH_EXTRACT = "WorkbookWithExtract.twbx" @@ -67,6 +68,7 @@ def get_publishable_name(file_value: str) -> str: USERS_DETAILS_FILE = "detailed_users.csv" USERNAMES_FILE = "usernames.csv" + def _test_command(test_args: list[str]): # this will raise an exception if it gets a non-zero return code # that will bubble up and fail the test @@ -87,6 +89,7 @@ def _test_command(test_args: list[str]): print(calling_args) return subprocess.check_call(calling_args) + class TabcmdCall: # Individual methods that implement a command @@ -121,9 +124,7 @@ def _publish_args(file, name, optional_args=None): arguments.append(optional_args) return arguments - def _publish_creds_args( - arguments, db_user=None, db_pass=None, db_save=None, oauth_user=None, oauth_save=None - ): + def _publish_creds_args(arguments, db_user=None, db_pass=None, db_save=None, oauth_user=None, oauth_save=None): if db_user: arguments.append("--db-username") arguments.append(db_user) @@ -229,9 +230,9 @@ def _list(item_type: str): arguments = [command, item_type] _test_command(arguments) + # test cases that use the API calls class OnlineCommandTest(unittest.TestCase): - @classmethod def setup_class(cls): print("running python -m") @@ -398,7 +399,7 @@ def test_wb_publish(self): @pytest.mark.order(11) def test_wb_get(self): # add .twbx to the end to tell the server what we are getting - name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT)+".twbx" + name_on_server = TestAssets.get_publishable_name(TestAssets.TWBX_FILE_WITH_EXTRACT) + ".twbx" TabcmdCall._get_workbook(name_on_server) @pytest.mark.order(11) From 8765eef47f94d541fe11e23ef24c72c02516fd15 Mon Sep 17 00:00:00 2001 From: Jac Date: Thu, 9 Oct 2025 16:27:12 -0700 Subject: [PATCH 5/9] Update tests/e2e/online_tests.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/e2e/online_tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e/online_tests.py b/tests/e2e/online_tests.py index 5a0c34ba..41716be1 100644 --- a/tests/e2e/online_tests.py +++ b/tests/e2e/online_tests.py @@ -47,6 +47,7 @@ class TestAssets: # to avoid this, we add our own random int to each name so we actually know what it is # this is why we have workbook_name+unique everywhere # BUG: this means you pretty much can't run a random individual test case without giving the already-unique name + @staticmethod def get_publishable_name(file_value: str) -> str: return os.path.splitext(os.path.basename(file_value))[0] + TestAssets.unique From f38bfc16594eb6a28404fbae5e9c8389f57de176 Mon Sep 17 00:00:00 2001 From: Jac Date: Thu, 9 Oct 2025 16:27:36 -0700 Subject: [PATCH 6/9] Update tests/e2e/online_tests.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/e2e/online_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/online_tests.py b/tests/e2e/online_tests.py index 41716be1..32547efd 100644 --- a/tests/e2e/online_tests.py +++ b/tests/e2e/online_tests.py @@ -384,7 +384,7 @@ def test_delete_projects(self): pytest.skip("Must be project administrator to create projects") TabcmdCall._delete_project(TestAssets.parent_location) TabcmdCall._delete_project("project_name_2", default_project_name) # project 2 - self._delete_project(default_project_name) + TabcmdCall._delete_project(default_project_name) @pytest.mark.order(10) def test_wb_publish(self): From 6f8ccdbdf70ea9ea349b4d369d026ca067788167 Mon Sep 17 00:00:00 2001 From: Jac Date: Thu, 9 Oct 2025 16:31:43 -0700 Subject: [PATCH 7/9] Add static methods for project and extract management --- tests/e2e/online_tests.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/e2e/online_tests.py b/tests/e2e/online_tests.py index 32547efd..88e28d0d 100644 --- a/tests/e2e/online_tests.py +++ b/tests/e2e/online_tests.py @@ -95,6 +95,7 @@ class TabcmdCall: # Individual methods that implement a command + @staticmethod def _create_project(project_name, parent_path=None): command = "createproject" arguments = [command, "--name", project_name] @@ -105,6 +106,7 @@ def _create_project(project_name, parent_path=None): arguments.append("--continue-if-exists") _test_command(arguments) + @staticmethod def _delete_project(project_name, parent_path=None): command = "deleteproject" arguments = [command, project_name] @@ -118,6 +120,7 @@ def _publish_samples(project_name): arguments = [command, "--name", project_name] _test_command(arguments) + @staticmethod def _publish_args(file, name, optional_args=None): command = "publish" arguments = [command, file, "--name", name, "--project", default_project_name, "--overwrite"] @@ -125,6 +128,7 @@ def _publish_args(file, name, optional_args=None): arguments.append(optional_args) return arguments + @staticmethod def _publish_creds_args(arguments, db_user=None, db_pass=None, db_save=None, oauth_user=None, oauth_save=None): if db_user: arguments.append("--db-username") @@ -141,16 +145,19 @@ def _publish_creds_args(arguments, db_user=None, db_pass=None, db_save=None, oau arguments.append("--save-oauth") return arguments + @staticmethod def _delete_wb(name): command = "delete" arguments = [command, "--project", default_project_name, name] _test_command(arguments) + @staticmethod def _delete_ds(name): command = "delete" arguments = [command, "--project", default_project_name, "--datasource", name] _test_command(arguments) + @staticmethod def _get_view(wb_name_on_server, sheet_name, filename=None, additional_args=None): server_file = "/views/" + wb_name_on_server + "/" + sheet_name command = "get" @@ -166,6 +173,7 @@ def _get_custom_view(self): command = "get" raise NotImplementedError("get_custom_view is not implemented") + @staticmethod def _export_wb(friendly_name, filename=None, additional_args=None): command = "export" arguments = [command, friendly_name, "--fullpdf"] @@ -176,6 +184,7 @@ def _export_wb(friendly_name, filename=None, additional_args=None): arguments = arguments + additional_args _test_command(arguments) + @staticmethod def _export_view(wb_name_on_server, sheet_name, export_type, filename=None, additional_args=None): server_file = "/" + wb_name_on_server + "/" + sheet_name command = "export" @@ -193,12 +202,14 @@ def _get_workbook(server_file): _test_command(arguments) os.path.exists("get_workbook.twbx") + @staticmethod def _get_datasource(server_file): command = "get" server_file = "/datasources/" + server_file arguments = [command, server_file] _test_command(arguments) + @staticmethod def _create_extract(item_name, type="-w"): command = "createextracts" arguments = [command, type, item_name, "--project", default_project_name] @@ -207,6 +218,7 @@ def _create_extract(item_name, type="-w"): _test_command(arguments) # variation: url + @staticmethod def _refresh_extract(item_name, type="-w"): command = "refreshextracts" arguments = [command, type, item_name, "--project", default_project_name] # bug: should not need -w @@ -221,11 +233,13 @@ def _refresh_extract(item_name, type="-w"): else: raise e + @staticmethod def _delete_extract(item_name, type="-w"): command = "deleteextracts" arguments = [command, type, item_name, "--include-all", "--project", default_project_name] _test_command(arguments) + @staticmethod def _list(item_type: str): command = "list" arguments = [command, item_type] From 5143c935de37e37a59ce6f690dd6fee2fa4040bb Mon Sep 17 00:00:00 2001 From: Jac Date: Thu, 9 Oct 2025 16:34:24 -0700 Subject: [PATCH 8/9] Add static methods for publishing and getting views --- tests/e2e/online_tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/e2e/online_tests.py b/tests/e2e/online_tests.py index 88e28d0d..9c9ee834 100644 --- a/tests/e2e/online_tests.py +++ b/tests/e2e/online_tests.py @@ -115,6 +115,7 @@ def _delete_project(project_name, parent_path=None): arguments.append(parent_path) _test_command(arguments) + @staticmethod def _publish_samples(project_name): command = "publishsamples" arguments = [command, "--name", project_name] @@ -168,6 +169,7 @@ def _get_view(wb_name_on_server, sheet_name, filename=None, additional_args=None arguments = arguments + additional_args _test_command(arguments) + @staticmethod def _get_custom_view(self): # TODO command = "get" @@ -195,6 +197,7 @@ def _export_view(wb_name_on_server, sheet_name, export_type, filename=None, addi arguments = arguments + additional_args _test_command(arguments) + @staticmethod def _get_workbook(server_file): command = "get" server_file = "/workbooks/" + server_file From 1dfe11714e124e5b10ae83584ac5fd9ff57d1bd7 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Fri, 10 Oct 2025 11:11:31 -0700 Subject: [PATCH 9/9] format --- tests/e2e/online_tests.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/e2e/online_tests.py b/tests/e2e/online_tests.py index 9c9ee834..226fbf72 100644 --- a/tests/e2e/online_tests.py +++ b/tests/e2e/online_tests.py @@ -95,7 +95,7 @@ class TabcmdCall: # Individual methods that implement a command - @staticmethod + @staticmethod def _create_project(project_name, parent_path=None): command = "createproject" arguments = [command, "--name", project_name] @@ -106,7 +106,7 @@ def _create_project(project_name, parent_path=None): arguments.append("--continue-if-exists") _test_command(arguments) - @staticmethod + @staticmethod def _delete_project(project_name, parent_path=None): command = "deleteproject" arguments = [command, project_name] @@ -115,13 +115,13 @@ def _delete_project(project_name, parent_path=None): arguments.append(parent_path) _test_command(arguments) - @staticmethod + @staticmethod def _publish_samples(project_name): command = "publishsamples" arguments = [command, "--name", project_name] _test_command(arguments) - @staticmethod + @staticmethod def _publish_args(file, name, optional_args=None): command = "publish" arguments = [command, file, "--name", name, "--project", default_project_name, "--overwrite"] @@ -129,7 +129,7 @@ def _publish_args(file, name, optional_args=None): arguments.append(optional_args) return arguments - @staticmethod + @staticmethod def _publish_creds_args(arguments, db_user=None, db_pass=None, db_save=None, oauth_user=None, oauth_save=None): if db_user: arguments.append("--db-username") @@ -146,19 +146,19 @@ def _publish_creds_args(arguments, db_user=None, db_pass=None, db_save=None, oau arguments.append("--save-oauth") return arguments - @staticmethod + @staticmethod def _delete_wb(name): command = "delete" arguments = [command, "--project", default_project_name, name] _test_command(arguments) - @staticmethod + @staticmethod def _delete_ds(name): command = "delete" arguments = [command, "--project", default_project_name, "--datasource", name] _test_command(arguments) - @staticmethod + @staticmethod def _get_view(wb_name_on_server, sheet_name, filename=None, additional_args=None): server_file = "/views/" + wb_name_on_server + "/" + sheet_name command = "get" @@ -169,13 +169,13 @@ def _get_view(wb_name_on_server, sheet_name, filename=None, additional_args=None arguments = arguments + additional_args _test_command(arguments) - @staticmethod + @staticmethod def _get_custom_view(self): # TODO command = "get" raise NotImplementedError("get_custom_view is not implemented") - @staticmethod + @staticmethod def _export_wb(friendly_name, filename=None, additional_args=None): command = "export" arguments = [command, friendly_name, "--fullpdf"] @@ -186,7 +186,7 @@ def _export_wb(friendly_name, filename=None, additional_args=None): arguments = arguments + additional_args _test_command(arguments) - @staticmethod + @staticmethod def _export_view(wb_name_on_server, sheet_name, export_type, filename=None, additional_args=None): server_file = "/" + wb_name_on_server + "/" + sheet_name command = "export" @@ -197,7 +197,7 @@ def _export_view(wb_name_on_server, sheet_name, export_type, filename=None, addi arguments = arguments + additional_args _test_command(arguments) - @staticmethod + @staticmethod def _get_workbook(server_file): command = "get" server_file = "/workbooks/" + server_file @@ -205,14 +205,14 @@ def _get_workbook(server_file): _test_command(arguments) os.path.exists("get_workbook.twbx") - @staticmethod + @staticmethod def _get_datasource(server_file): command = "get" server_file = "/datasources/" + server_file arguments = [command, server_file] _test_command(arguments) - @staticmethod + @staticmethod def _create_extract(item_name, type="-w"): command = "createextracts" arguments = [command, type, item_name, "--project", default_project_name] @@ -221,7 +221,7 @@ def _create_extract(item_name, type="-w"): _test_command(arguments) # variation: url - @staticmethod + @staticmethod def _refresh_extract(item_name, type="-w"): command = "refreshextracts" arguments = [command, type, item_name, "--project", default_project_name] # bug: should not need -w @@ -236,13 +236,13 @@ def _refresh_extract(item_name, type="-w"): else: raise e - @staticmethod + @staticmethod def _delete_extract(item_name, type="-w"): command = "deleteextracts" arguments = [command, type, item_name, "--include-all", "--project", default_project_name] _test_command(arguments) - @staticmethod + @staticmethod def _list(item_type: str): command = "list" arguments = [command, item_type]