diff --git a/tabcmd/commands/datasources_and_workbooks/publish_command.py b/tabcmd/commands/datasources_and_workbooks/publish_command.py index dfd26b28..0a94cacb 100644 --- a/tabcmd/commands/datasources_and_workbooks/publish_command.py +++ b/tabcmd/commands/datasources_and_workbooks/publish_command.py @@ -42,10 +42,27 @@ def define_args(publish_parser): set_append_replace_option(group) set_parent_project_arg(group) + @classmethod + def _require_db_server_for_workbook(cls, args, filename, logger): + """Fail fast if the caller is publishing a workbook with embedded + credentials but did not supply --db-server. Called both up front + (direct-file case) and inside the per-file publish loop (folder case).""" + if (args.db_username or args.oauth_username) and not args.db_server: + filename_lower = (filename or "").lower() + if filename_lower.endswith(".twb") or filename_lower.endswith(".twbx"): + Errors.exit_with_error(logger, _("publish.errors.db_server_required")) + @classmethod def run_command(cls, args): logger = log(cls.__name__, args.logging_level) logger.debug(_("tabcmd.launching")) + + # Fail fast before auth when the direct target is clearly a workbook file: + # TSC's workbook publish path requires ConnectionItem.server_address whenever + # embedded credentials are supplied. Datasource publishes never need it, so + # this check is deliberately scoped to workbook extensions. + PublishCommand._require_db_server_for_workbook(args, args.filename, logger) + session = Session() server = session.create_session(args, logger) @@ -88,6 +105,9 @@ def run_command(cls, args): source = PublishCommand.get_filename_extension_if_tableau_type(logger, str_filename) logger.info(_("publish.status").format(str_filename)) if source in ["twbx", "twb"]: + # TSC's workbook publish path requires ConnectionItem.server_address whenever + # embedded connection credentials are supplied; datasource publish does not. + PublishCommand._require_db_server_for_workbook(args, str_filename, logger) try: published_item = PublishCommand.publish_workbook_file( args=args, @@ -96,7 +116,7 @@ def run_command(cls, args): project_id=project_id, str_filename=str_filename, publish_mode=publish_mode, - credentials=workbook_connections, + connection=workbook_connections, ) except Exception as e: Errors.exit_with_error(logger, exception=e) @@ -172,7 +192,7 @@ def get_publish_mode(args, logger): return publish_mode @staticmethod - def publish_workbook_file(args, logger, server, project_id, str_filename, publish_mode, credentials): + def publish_workbook_file(args, logger, server, project_id, str_filename, publish_mode, connection): if args.thumbnail_group: raise AttributeError("Generating thumbnails for a group is not yet implemented.") if args.thumbnail_username and args.thumbnail_group: @@ -185,7 +205,7 @@ def publish_workbook_file(args, logger, server, project_id, str_filename, publis publish_mode, # args.thumbnail_username, not yet implemented in tsc # args.thumbnail_group, - connections=[credentials] if credentials else None, + connections=[connection] if connection else None, as_job=False, skip_connection_check=args.skip_connection_check, ) diff --git a/tabcmd/locales/de/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/de/LC_MESSAGES/tabcmd.mo index 30f3641a..1e8df067 100644 Binary files a/tabcmd/locales/de/LC_MESSAGES/tabcmd.mo and b/tabcmd/locales/de/LC_MESSAGES/tabcmd.mo differ diff --git a/tabcmd/locales/de/tabcmd_messages_de.properties b/tabcmd/locales/de/tabcmd_messages_de.properties index 0faed37e..01efd21f 100644 --- a/tabcmd/locales/de/tabcmd_messages_de.properties +++ b/tabcmd/locales/de/tabcmd_messages_de.properties @@ -75,6 +75,7 @@ logout.short_description=Vom Server abmelden publish.description=Arbeitsmappe, Datenquelle oder Extrakt auf dem Server veröffentlichen publish.errors.unexpected_server_response=Unerwartete Antwort vom Server: {0} publish.errors.server_resource_not_found=Die von Ihnen angegebene Ressource exisitiert nicht oder Sie haben nicht die Berechtigung, sie einzusehen. Prüfen Sie Ihren Projektnamen und Ihre Berechtigungen. +publish.errors.db_server_required=--db-server is required when publishing a workbook with --db-username or --oauth-username. Use --db-server to specify the database server address the embedded credentials should be associated with. publish.options.append=Extraktdatei an vorhandene Datenquelle anfügen publish.options.db-password=Datenbankkennwort für alle Datenquellen publish.options.db-server=Serveradresse der Datenbank, die den eingebetteten Verbindungsanmeldeinformationen zugeordnet werden soll. Erforderlich bei Verwendung von --db-username oder --oauth-username zum Veröffentlichen einer Arbeitsmappe. diff --git a/tabcmd/locales/en/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/en/LC_MESSAGES/tabcmd.mo index 7238ce24..85c42d08 100644 Binary files a/tabcmd/locales/en/LC_MESSAGES/tabcmd.mo and b/tabcmd/locales/en/LC_MESSAGES/tabcmd.mo differ diff --git a/tabcmd/locales/en/tabcmd_messages_en.properties b/tabcmd/locales/en/tabcmd_messages_en.properties index e5274230..88c40483 100644 --- a/tabcmd/locales/en/tabcmd_messages_en.properties +++ b/tabcmd/locales/en/tabcmd_messages_en.properties @@ -75,9 +75,10 @@ logout.short_description=Sign out from the server publish.description=Publish a workbook, data source, or extract to the server publish.errors.unexpected_server_response=Unexpected response from the server: {0} publish.errors.server_resource_not_found=The resource you specified does not exist, or you do not have permission to see it. Check your project name and permissions +publish.errors.db_server_required=--db-server is required when publishing a workbook with --db-username or --oauth-username. Use --db-server to specify the database server address the embedded credentials should be associated with. publish.options.append=Append extract file to existing data source publish.options.db-password=Database password for all data sources -publish.options.db-server=Server address of the database to associate with the embedded connection credentials. Required when using --db-username or --oauth-username to publish a workbook. +publish.options.db-server=Server address of the database to associate with the embedded connection credentials. Required when using --db-username or --oauth-username to publish a workbook. Must match the connection server stored in the workbook; a mismatch silently drops the embedded credentials. publish.options.db-username=Database username for all data sources publish.options.encrypt_extracts=Encrypt extracts in the workbook or datasource being published to the server. publish.options.name=Workbook or data source name on the server. If omitted, the workbook or data source will be named after the file name, without the twb(x), tds(x), or tde extension. Publishing a .tde file will create a data source diff --git a/tabcmd/locales/es/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/es/LC_MESSAGES/tabcmd.mo index aef0790b..f0ec3524 100644 Binary files a/tabcmd/locales/es/LC_MESSAGES/tabcmd.mo and b/tabcmd/locales/es/LC_MESSAGES/tabcmd.mo differ diff --git a/tabcmd/locales/es/tabcmd_messages_es.properties b/tabcmd/locales/es/tabcmd_messages_es.properties index 78a747af..b9ff11fd 100644 --- a/tabcmd/locales/es/tabcmd_messages_es.properties +++ b/tabcmd/locales/es/tabcmd_messages_es.properties @@ -75,6 +75,7 @@ logout.short_description=Cerrar sesión en el servidor publish.description=Publicar un libro de trabajo, una fuente de datos o una extracción en el servidor publish.errors.unexpected_server_response=Respuesta inesperada del servidor: {0} publish.errors.server_resource_not_found=El recurso que ha especificado no existe o no tiene permiso para verlo. Compruebe el nombre del proyecto y sus permisos +publish.errors.db_server_required=--db-server is required when publishing a workbook with --db-username or --oauth-username. Use --db-server to specify the database server address the embedded credentials should be associated with. publish.options.append=Adjuntar archivo de extracción a fuente de datos existente publish.options.db-password=Contraseña de base de datos para todas las fuentes de datos publish.options.db-server=Dirección del servidor de la base de datos que se asociará con las credenciales de conexión incrustadas. Obligatorio cuando se usa --db-username o --oauth-username para publicar un libro de trabajo. diff --git a/tabcmd/locales/fr/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/fr/LC_MESSAGES/tabcmd.mo index b421c6f1..ebd31ac0 100644 Binary files a/tabcmd/locales/fr/LC_MESSAGES/tabcmd.mo and b/tabcmd/locales/fr/LC_MESSAGES/tabcmd.mo differ diff --git a/tabcmd/locales/fr/tabcmd_messages_fr.properties b/tabcmd/locales/fr/tabcmd_messages_fr.properties index 32744778..c7b616a0 100644 --- a/tabcmd/locales/fr/tabcmd_messages_fr.properties +++ b/tabcmd/locales/fr/tabcmd_messages_fr.properties @@ -75,6 +75,7 @@ logout.short_description=Se déconnecter du serveur publish.description=Publier un classeur, une source de données ou un extrait sur le serveur publish.errors.unexpected_server_response=Réponse imprévue du serveur : {0} publish.errors.server_resource_not_found=La ressource spécifiée n’existe pas, ou vous n’êtes pas autorisé à la voir. Consultez le nom de votre projet et les autorisations. +publish.errors.db_server_required=--db-server is required when publishing a workbook with --db-username or --oauth-username. Use --db-server to specify the database server address the embedded credentials should be associated with. publish.options.append=Ajout d’un fichier d’extrait à une source de données existante publish.options.db-password=Mot de passe de base de données pour toutes les sources de données. publish.options.db-server=Adresse du serveur de la base de données à associer aux informations d’identification de connexion intégrées. Obligatoire lors de l’utilisation de --db-username ou --oauth-username pour publier un classeur. diff --git a/tabcmd/locales/ga/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/ga/LC_MESSAGES/tabcmd.mo index 3bbbf4c2..d6eb5def 100644 Binary files a/tabcmd/locales/ga/LC_MESSAGES/tabcmd.mo and b/tabcmd/locales/ga/LC_MESSAGES/tabcmd.mo differ diff --git a/tabcmd/locales/ga/tabcmd_messages_ga.properties b/tabcmd/locales/ga/tabcmd_messages_ga.properties index 320638ea..04bb515a 100644 --- a/tabcmd/locales/ga/tabcmd_messages_ga.properties +++ b/tabcmd/locales/ga/tabcmd_messages_ga.properties @@ -75,6 +75,7 @@ logout.short_description=6dd8-表:Sign out from the server|桜 publish.description=60cd-表:Publish a workbook, data source, or extract to the server|桜 publish.errors.unexpected_server_response=dbf6-表:Unexpected response from the server: {0}|桜 publish.errors.server_resource_not_found=b7e8-表:The resource you specified does not exist, or you do not have permission to see it. Check your project name and permissions|桜 +publish.errors.db_server_required=--db-server is required when publishing a workbook with --db-username or --oauth-username. Use --db-server to specify the database server address the embedded credentials should be associated with. publish.options.append=6393-表:Append extract file to existing data source|桜 publish.options.db-password=ab47-表:Database password for all data sources|桜 publish.options.db-server=3166-表:Server address of the database to associate with the embedded connection credentials. Required when using --db-username or --oauth-username to publish a workbook.|桜 diff --git a/tabcmd/locales/it/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/it/LC_MESSAGES/tabcmd.mo index 58c7dab1..1443fa34 100644 Binary files a/tabcmd/locales/it/LC_MESSAGES/tabcmd.mo and b/tabcmd/locales/it/LC_MESSAGES/tabcmd.mo differ diff --git a/tabcmd/locales/it/tabcmd_messages_it.properties b/tabcmd/locales/it/tabcmd_messages_it.properties index e31ef9b8..3478b9ba 100644 --- a/tabcmd/locales/it/tabcmd_messages_it.properties +++ b/tabcmd/locales/it/tabcmd_messages_it.properties @@ -75,6 +75,7 @@ logout.short_description=Disconnetti dal server publish.description=Pubblica una cartella di lavoro, un’origine dati o un’estrazione sul server publish.errors.unexpected_server_response=Risposta inaspettata dal server: {0} publish.errors.server_resource_not_found=La risorsa specificata non esiste o non si ha l’autorizzazione a visualizzarla. Controlla il nome del progetto e le autorizzazioni +publish.errors.db_server_required=--db-server is required when publishing a workbook with --db-username or --oauth-username. Use --db-server to specify the database server address the embedded credentials should be associated with. publish.options.append=Aggiungi un file di estrazione all’origine dati esistente publish.options.db-password=Password del database per tutte le origini dati publish.options.db-server=Indirizzo del server del database da associare alle credenziali di connessione incorporate. Obbligatorio quando si usa --db-username o --oauth-username per pubblicare una cartella di lavoro. diff --git a/tabcmd/locales/ja/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/ja/LC_MESSAGES/tabcmd.mo index 8bec7944..5e000352 100644 Binary files a/tabcmd/locales/ja/LC_MESSAGES/tabcmd.mo and b/tabcmd/locales/ja/LC_MESSAGES/tabcmd.mo differ diff --git a/tabcmd/locales/ja/tabcmd_messages_ja.properties b/tabcmd/locales/ja/tabcmd_messages_ja.properties index 27c911a2..59d48b8d 100644 --- a/tabcmd/locales/ja/tabcmd_messages_ja.properties +++ b/tabcmd/locales/ja/tabcmd_messages_ja.properties @@ -75,6 +75,7 @@ logout.short_description=サーバーからサインアウト publish.description=ワークブック、データ ソース、または抽出をサーバーにパブリッシュ publish.errors.unexpected_server_response=サーバーからの予期しない応答: {0} publish.errors.server_resource_not_found=指定されたリソースは存在しないか、そのリソースを表示できるパーミッションがありません。プロジェクト名とパーミッションを確認してください。 +publish.errors.db_server_required=--db-server is required when publishing a workbook with --db-username or --oauth-username. Use --db-server to specify the database server address the embedded credentials should be associated with. publish.options.append=抽出ファイルを既存のデータ ソースに追加 publish.options.db-password=すべてのデータ ソースのデータベース パスワード publish.options.db-server=埋め込まれた接続資格情報に関連付けるデータベースのサーバー アドレス。ワークブックの発行時に --db-username または --oauth-username を使用する場合は必須です。 diff --git a/tabcmd/locales/ko/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/ko/LC_MESSAGES/tabcmd.mo index 432aa94d..2a7894f0 100644 Binary files a/tabcmd/locales/ko/LC_MESSAGES/tabcmd.mo and b/tabcmd/locales/ko/LC_MESSAGES/tabcmd.mo differ diff --git a/tabcmd/locales/ko/tabcmd_messages_ko.properties b/tabcmd/locales/ko/tabcmd_messages_ko.properties index 3a764340..eb9e8057 100644 --- a/tabcmd/locales/ko/tabcmd_messages_ko.properties +++ b/tabcmd/locales/ko/tabcmd_messages_ko.properties @@ -75,6 +75,7 @@ logout.short_description=서버에서 로그아웃 publish.description=서버에 통합 문서, 데이터 원본 또는 추출 게시 publish.errors.unexpected_server_response=서버의 예기치 않은 응답: {0} publish.errors.server_resource_not_found=지정한 리소스가 존재하지 않거나 사용자에게 리소스를 볼 수 있는 권한이 없습니다. 프로젝트 이름 및 사용 권한을 확인하십시오. +publish.errors.db_server_required=--db-server is required when publishing a workbook with --db-username or --oauth-username. Use --db-server to specify the database server address the embedded credentials should be associated with. publish.options.append=기존 데이터 원본에 추출 파일 추가 publish.options.db-password=모든 데이터 원본에 대한 데이터베이스 비밀번호 publish.options.db-server=포함된 연결 자격 증명과 연결할 데이터베이스의 서버 주소입니다. 워크북을 게시할 때 --db-username 또는 --oauth-username을 사용하는 경우 필수입니다. diff --git a/tabcmd/locales/pt/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/pt/LC_MESSAGES/tabcmd.mo index 8929f104..80804419 100644 Binary files a/tabcmd/locales/pt/LC_MESSAGES/tabcmd.mo and b/tabcmd/locales/pt/LC_MESSAGES/tabcmd.mo differ diff --git a/tabcmd/locales/pt/tabcmd_messages_pt.properties b/tabcmd/locales/pt/tabcmd_messages_pt.properties index c9dbfddd..edc2443b 100644 --- a/tabcmd/locales/pt/tabcmd_messages_pt.properties +++ b/tabcmd/locales/pt/tabcmd_messages_pt.properties @@ -75,9 +75,10 @@ logout.short_description=Fazer logoff do servidor publish.description=Publique uma pasta de trabalho, fonte de dados ou extração no servidor publish.errors.unexpected_server_response=Resposta inesperada do servidor: {0} publish.errors.server_resource_not_found=O recurso especificado não existe ou você não tem permissão para vê-lo. Verifique o nome e as permissões do projeto +publish.errors.db_server_required=--db-server is required when publishing a workbook with --db-username or --oauth-username. Use --db-server to specify the database server address the embedded credentials should be associated with. publish.options.append=Anexe o arquivo de extração à fonte de dados existente publish.options.db-password=Senha do banco de dados para todas as fontes de dados -publish.options.db-server=Endereço do servidor do banco de dado associado as credenciais. Obrigatório ao usar --db-username ou --oauth-username. +publish.options.db-server=Endereço do servidor do banco de dados associado às credenciais. Obrigatório ao usar --db-username ou --oauth-username. publish.options.db-username=Nome de usuário do banco de dados para todas as fontes de dados publish.options.encrypt_extracts=Criptografar extrações no servidor publish.options.name=O nome da pasta de trabalho/fonte de dados no servidor. Se omitido, a pasta de trabalho/fonte de dados será nomeado de acordo com nome de arquivo, sem a extensão twb(x), tds(x) ou tde. Publicar um arquivo .tde criará uma fonte de dados diff --git a/tabcmd/locales/sv/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/sv/LC_MESSAGES/tabcmd.mo index e0e5059e..a8f6cf78 100644 Binary files a/tabcmd/locales/sv/LC_MESSAGES/tabcmd.mo and b/tabcmd/locales/sv/LC_MESSAGES/tabcmd.mo differ diff --git a/tabcmd/locales/sv/tabcmd_messages_sv.properties b/tabcmd/locales/sv/tabcmd_messages_sv.properties index ccf9ea64..7af17ff2 100644 --- a/tabcmd/locales/sv/tabcmd_messages_sv.properties +++ b/tabcmd/locales/sv/tabcmd_messages_sv.properties @@ -75,6 +75,7 @@ logout.short_description=Logga ut från servern publish.description=Publicera en arbetsbok, en datakälla eller ett extrakt på servern publish.errors.unexpected_server_response=Oväntat svar från servern: {0} publish.errors.server_resource_not_found=Den resurs du angav finns inte, eller så har du inte behörighet att se den. Kontrollera ditt projektnamn och dina behörigheter +publish.errors.db_server_required=--db-server is required when publishing a workbook with --db-username or --oauth-username. Use --db-server to specify the database server address the embedded credentials should be associated with. publish.options.append=Lägg till extraktfil till befintlig datakälla publish.options.db-password=Databaslösenord för alla datakällor publish.options.db-server=Serveradress för databasen som ska associeras med de inbäddade anslutningsuppgifterna. Krävs när --db-username eller --oauth-username används för att publicera en arbetsbok. diff --git a/tabcmd/locales/zh/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/zh/LC_MESSAGES/tabcmd.mo index 68a359d9..ff3767c3 100644 Binary files a/tabcmd/locales/zh/LC_MESSAGES/tabcmd.mo and b/tabcmd/locales/zh/LC_MESSAGES/tabcmd.mo differ diff --git a/tabcmd/locales/zh/tabcmd_messages_zh.properties b/tabcmd/locales/zh/tabcmd_messages_zh.properties index 8a1d0825..72df2c8a 100644 --- a/tabcmd/locales/zh/tabcmd_messages_zh.properties +++ b/tabcmd/locales/zh/tabcmd_messages_zh.properties @@ -75,6 +75,7 @@ logout.short_description=从服务器中退出 publish.description=将工作簿、数据源或数据提取发布到服务器 publish.errors.unexpected_server_response=来自服务器的意外响应: {0} publish.errors.server_resource_not_found=指定的资源不存在,或者您没有查看该资源的权限。请检查您的项目名称和权限 +publish.errors.db_server_required=--db-server is required when publishing a workbook with --db-username or --oauth-username. Use --db-server to specify the database server address the embedded credentials should be associated with. publish.options.append=将数据提取文件追加到现有数据源 publish.options.db-password=用于所有数据源的数据库密码 publish.options.db-server=要与嵌入式连接凭据关联的数据库的服务器地址。使用 --db-username 或 --oauth-username 发布工作簿时必需。 diff --git a/tests/commands/test_publish_command.py b/tests/commands/test_publish_command.py index df4b7115..5c54bc6e 100644 --- a/tests/commands/test_publish_command.py +++ b/tests/commands/test_publish_command.py @@ -72,7 +72,7 @@ def test_publish_with_creds(self, mock_path, mock_glob, mock_session): self.assertEqual(len(connections), 1) self.assertEqual(connections[0].server_address, "db.example.com") - def test_publish_with_creds_no_db_server(self, mock_path, mock_glob, mock_session): + def test_publish_with_db_username_missing_db_server_exits(self, mock_path, mock_glob, mock_session): set_up_mock_server(mock_session) mock_path = set_up_mock_path(mock_path) @@ -87,7 +87,7 @@ def test_publish_with_creds_no_db_server(self, mock_path, mock_glob, mock_sessio mock_args.tabbed = True mock_args.db_username = "username" - mock_args.db_password = "oauth_u" + mock_args.db_password = "password" mock_args.db_server = None mock_args.save_db_password = True mock_args.oauth_username = None @@ -97,13 +97,111 @@ def test_publish_with_creds_no_db_server(self, mock_path, mock_glob, mock_sessio mock_args.thumbnail_group = None mock_args.skip_connection_check = False + with self.assertRaises(SystemExit): + PublishCommand.run_command(mock_args) + mock_session.internal_server.workbooks.publish.assert_not_called() + + def test_publish_with_oauth_username_missing_db_server_exits(self, mock_path, mock_glob, mock_session): + set_up_mock_server(mock_session) + mock_path = set_up_mock_path(mock_path) + + mock_args.overwrite = False + mock_args.append = True + mock_args.replace = False + + mock_args.filename = "existing_file.twbx" + mock_args.project_name = "project-name" + mock_args.parent_project_path = "projects" + mock_args.name = "" + mock_args.tabbed = True + + mock_args.db_username = None + mock_args.db_password = None + mock_args.db_server = None + mock_args.save_db_password = False + mock_args.oauth_username = "oauth_user" + mock_args.save_oauth = True + mock_args.embed = False + + mock_args.thumbnail_username = None + mock_args.thumbnail_group = None + mock_args.skip_connection_check = False + + with self.assertRaises(SystemExit): + PublishCommand.run_command(mock_args) + mock_session.internal_server.workbooks.publish.assert_not_called() + + def test_publish_with_oauth_creds(self, mock_path, mock_glob, mock_session): + set_up_mock_server(mock_session) + mock_path = set_up_mock_path(mock_path) + + mock_args.overwrite = False + mock_args.append = True + mock_args.replace = False + + mock_args.filename = "existing_file.twbx" + mock_args.project_name = "project-name" + mock_args.parent_project_path = "projects" + mock_args.name = "" + mock_args.tabbed = True + + mock_args.db_username = None + mock_args.db_password = None + mock_args.save_db_password = False + mock_args.oauth_username = "oauth_user" + mock_args.save_oauth = True + mock_args.db_server = "db.example.com" + mock_args.embed = False + + mock_args.thumbnail_username = None + mock_args.thumbnail_group = None + mock_args.skip_connection_check = False + PublishCommand.run_command(mock_args) mock_session.internal_server.workbooks.publish.assert_called() call_kwargs = mock_session.internal_server.workbooks.publish.call_args.kwargs connections = call_kwargs["connections"] self.assertEqual(len(connections), 1) - self.assertIsNone(connections[0].server_address) + self.assertEqual(connections[0].server_address, "db.example.com") + + def test_publish_datasource_with_db_username_no_db_server(self, mock_path, mock_glob, mock_session): + # Datasource publishes send credentials via _add_credentials_element on the TSC side, + # which does not require server_address. --db-server must not be required here. + set_up_mock_server(mock_session) + # set_up_mock_server only wires .workbooks on the mocked server (TSC.Server class-spec + # doesn't expose instance attributes from __init__); wire .datasources by hand. + mock_session.internal_server.datasources = mock.MagicMock() + mock_path = set_up_mock_path(mock_path) + # set_up_mock_path hardcodes splitext -> [_, 'twbx']; override so extension routing + # actually sees a datasource here. + mock_path.splitext = lambda x: ["file", "tdsx"] + + mock_args.overwrite = False + mock_args.append = True + mock_args.replace = False + + mock_args.filename = "existing_file.tdsx" + mock_args.project_name = "project-name" + mock_args.parent_project_path = "projects" + mock_args.name = "" + mock_args.tabbed = True + + mock_args.db_username = "username" + mock_args.db_password = "password" + mock_args.db_server = None + mock_args.save_db_password = True + mock_args.oauth_username = None + mock_args.embed = False + mock_args.use_tableau_bridge = False + + mock_args.thumbnail_username = None + mock_args.thumbnail_group = None + mock_args.skip_connection_check = False + + PublishCommand.run_command(mock_args) + mock_session.internal_server.datasources.publish.assert_called() + mock_session.internal_server.workbooks.publish.assert_not_called() def test_get_files_to_publish_twbx(self, mock_path, mock_glob, mock_session): set_up_mock_server(mock_session) diff --git a/tests/e2e/online_tests.py b/tests/e2e/online_tests.py index 741af663..3e27dfff 100644 --- a/tests/e2e/online_tests.py +++ b/tests/e2e/online_tests.py @@ -75,6 +75,8 @@ def get_publishable_name(file_value: str) -> str: TDS_FILE_LIVE = "live_mysql.tds" TWB_FILE_WITH_EMBEDDED_CONNECTION = "EmbeddedCredentials.twb" + # server_address baked into the workbook's element. + TWB_FILE_EMBEDDED_CONNECTION_SERVER = "see-internal-slack" USERS_DETAILS_FILE = "detailed_users.csv" USERNAMES_FILE = "usernames.csv" @@ -142,7 +144,9 @@ def _publish_args(file, name, optional_args=None): return arguments @staticmethod - 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, db_server=None + ): if db_user: arguments.append("--db-username") arguments.append(db_user) @@ -156,6 +160,9 @@ def _publish_creds_args(arguments, db_user=None, db_pass=None, db_save=None, oau arguments.append(oauth_user) if oauth_save: arguments.append("--save-oauth") + if db_server: + arguments.append("--db-server") + arguments.append(db_server) return arguments @staticmethod @@ -468,14 +475,52 @@ def test_view_get_png(self): @pytest.mark.order(11) def test_wb_publish_embedded(self): + # NOTE: This asserts only that publish did not crash. It does NOT verify + # that credentials actually embedded on the server side - a mismatched + # --db-server would still exit 0 while silently dropping the creds at + # TSC's request-factory boundary. A stronger assertion would populate + # connections post-publish and check embed_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 = TabcmdCall._publish_creds_args( + arguments, + database_user, + database_password, + True, + db_server=TestAssets.TWB_FILE_EMBEDDED_CONNECTION_SERVER, + ) arguments.append("--tabbed") arguments.append("--skip-connection-check") _test_command(arguments) + @pytest.mark.order(11) + def test_wb_publish_embedded_missing_db_server_fails(self): + # Publish with --db-username but no --db-server must exit non-zero with our + # friendly message rather than a raw tableauserverclient traceback. The guard + # short-circuits before any real work, so throwaway credentials are safe here. + 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) + "-no-server" + arguments = TabcmdCall._publish_args(file, name_on_server) + arguments = TabcmdCall._publish_creds_args(arguments, "placeholder_user", "placeholder_pass", True) + arguments.append("--tabbed") + arguments.append("--skip-connection-check") + + login_args = setup_e2e.get_login_args() + if login_args is None: + pytest.skip("No credentials available (credentials.py not found)") + calling_args = ["python", "-m", "tabcmd"] + arguments + login_args + [debug_log] + ["--no-certcheck"] + result = subprocess.run(calling_args, capture_output=True, text=True) + + assert result.returncode != 0, "expected non-zero exit for missing --db-server" + # Require the rendered English guidance -- accepting the raw msgid would let a + # localization regression pass silently. Every locale ships an English placeholder + # for this key until real translations arrive. + combined = (result.stdout or "") + (result.stderr or "") + assert "--db-server is required" in combined, ( + "expected rendered guard message in output; got:\n" + combined + ) + @pytest.mark.order(12) def test_publish_ds(self): file = os.path.join("tests", "assets", TestAssets.TDSX_FILE_WITH_EXTRACT)