from thoughtspot_errors import friendly_mcp_liveboard_error from tests.e2e_quality import extract_db_schema_from_tml_docs import liveboard_creator import thoughtspot_deployer def test_friendly_mcp_error_classifies_service_outage(): message, category = friendly_mcp_liveboard_error( "MCP liveboard timed out after 360s — MCP endpoint unresponsive at network level" ) assert category == "mcp_service" assert "MCP service appears unavailable or timed out" in message assert "Raw MCP error:" in message def test_friendly_mcp_error_classifies_auth_failure(): message, category = friendly_mcp_liveboard_error( "Failed to get trusted auth token. Check THOUGHTSPOT_TRUSTED_AUTH_KEY" ) assert category == "mcp_auth" assert "could not authenticate" in message assert "trusted auth key" in message.lower() def test_friendly_mcp_error_classifies_generation_failure(): message, category = friendly_mcp_liveboard_error( "No questions generated. Model may be empty or incompatible." ) assert category == "mcp_generation" assert "MCP liveboard generation step" in message assert "falling back" in message def test_friendly_mcp_error_classifies_missing_answer_tokens_as_recoverable(): message, category = friendly_mcp_liveboard_error( "No liveboard-compatible answers with ThoughtSpot session/tokens were retrieved." ) assert category == "mcp_answer_tokens" assert "backup liveboard" in message def test_direct_answer_rejects_empty_token_payload(monkeypatch): class FakeResponse: status_code = 200 text = "" def json(self): return { "session_identifier": "session-123", "generation_number": -1, "message_type": "TSAnswer", "visualization_type": "Undefined", "tokens": "", "display_tokens": "", } class FakeSession: def post(self, *args, **kwargs): return FakeResponse() monkeypatch.setattr(liveboard_creator, "_get_direct_api_session", lambda *args, **kwargs: (FakeSession(), "token")) assert liveboard_creator._get_answer_direct( "Total Net Sales", "model-guid", "testrunner@example.com", "secret", ts_url="https://example.thoughtspot.cloud", ) is None def test_liveboard_answer_validation_requires_tokens_but_allows_undefined_visualization(): assert liveboard_creator._is_liveboard_answer_valid( { "session_identifier": "session-123", "tokens": [{"token": "Net Sales"}], "visualization_type": "Undefined", } ) assert not liveboard_creator._is_liveboard_answer_valid( { "session_identifier": "session-123", "tokens": "", "visualization_type": "Undefined", } ) def test_extract_db_schema_reads_physical_table_tml_not_model_name(): docs = [ """ model: name: TST_WRONG_01010000_ABC_mdl """, """ table: name: SALES db: DEMOBUILD schema: TST_RIGHT_01010000_DEF_sch """, ] assert extract_db_schema_from_tml_docs(docs) == ("DEMOBUILD", "TST_RIGHT_01010000_DEF_sch") def test_extract_db_schema_does_not_guess_from_model_name(): docs = [ """ model: name: TST_WRONG_01010000_ABC_mdl """ ] assert extract_db_schema_from_tml_docs(docs) == ("", "") def test_connection_create_reconciles_secure_store_bad_gateway(monkeypatch): class FakeResponse: status_code = 200 text = "" def json(self): return [ { "response": { "status": { "status_code": "ERROR", "error_message": ( "Exception occurred while saving dataSource: " "Error Code: SECURE_STORE_ERROR Error Message: Bad Gateway." ), }, "header": {"name": "MBB_VIZ_TEST_conn"}, } } ] class FakeSession: def __init__(self): self.posts = [] def post(self, url, **kwargs): self.posts.append({"url": url, **kwargs}) return FakeResponse() deployer = thoughtspot_deployer.ThoughtSpotDeployer.__new__( thoughtspot_deployer.ThoughtSpotDeployer ) deployer.base_url = "https://ts.example" deployer.session = FakeSession() deployer.headers = {} deployer.create_connection_tml = lambda name, database: "connection:\n name: MBB_VIZ_TEST_conn\n" calls = [] def fake_get_connection_by_name(name): calls.append(name) if len(calls) == 1: return None return {"header": {"id_guid": "connection-guid", "name": name}} deployer.get_connection_by_name = fake_get_connection_by_name monkeypatch.setattr(thoughtspot_deployer.time, "sleep", lambda seconds: None) guid, fqn = deployer.create_connection_with_reconcile( "MBB_VIZ_TEST_conn", "DEMOBUILD_TEST", log_progress=lambda message: None, ) assert guid == "connection-guid" assert fqn == "connection-guid" assert len(deployer.session.posts) == 1 assert len(calls) == 2 def test_trusted_auth_retries_transient_gateway_error(monkeypatch): class FakeResponse: def __init__(self, status_code, payload=None, text=""): self.status_code = status_code self._payload = payload or {} self.text = text def json(self): return self._payload class FakeSession: def __init__(self): self.headers = {} self.calls = 0 def post(self, url, **kwargs): self.calls += 1 if self.calls == 1: return FakeResponse(502, text="Bad Gateway") return FakeResponse(200, {"token": "token-123"}) deployer = thoughtspot_deployer.ThoughtSpotDeployer.__new__( thoughtspot_deployer.ThoughtSpotDeployer ) deployer.base_url = "https://ts.example" deployer.username = "mike.boone@thoughtspot.com" deployer.secret_key = "secret-key-value" deployer.session = FakeSession() deployer.last_auth_status_code = None deployer.last_auth_error = "" monkeypatch.setenv("TS_AUTH_MAX_ATTEMPTS", "2") monkeypatch.setenv("TS_AUTH_RETRY_WAIT_SECONDS", "1") monkeypatch.setattr(thoughtspot_deployer.time, "sleep", lambda seconds: None) assert deployer.authenticate_trusted() is True assert deployer.session.calls == 2 assert deployer.session.headers["Authorization"] == "Bearer token-123"