Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/ucode/databricks.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,10 @@ def has_valid_databricks_auth(workspace: str, profile: str | None = None) -> boo
if os.environ.get("DATABRICKS_BEARER", "").strip():
return True
_log_auth_diagnostics()
# Mirror run_databricks_login: when ~/.databrickscfg has multiple
# profiles for the same host, `databricks auth token --host …` refuses
# to disambiguate without --profile, so resolve it from the host here.
profile = profile or find_profile_name_for_host(workspace)
try:
env = build_databricks_cli_env(workspace)
result = run(
Expand Down Expand Up @@ -524,6 +528,9 @@ def get_databricks_token(
return bearer

_log_auth_diagnostics()
# See has_valid_databricks_auth: resolve the profile from the host when
# the caller didn't supply one, so duplicate-host cfgs don't break us.
profile = profile or find_profile_name_for_host(workspace)
env = build_databricks_cli_env(workspace)
cmd = [
"databricks",
Expand Down Expand Up @@ -588,13 +595,12 @@ def _fetch() -> str:
token = _fetch()

if not token:
profile_name = profile or find_profile_name_for_host(workspace)
stale_profile_hint = ""
if profile_name:
if profile:
stale_profile_hint = (
" The saved Databricks CLI profile may be stale or invalid. Try:\n"
f" databricks auth logout --profile {profile_name}\n"
f" databricks auth login --host {workspace} --profile {profile_name}"
f" databricks auth logout --profile {profile}\n"
f" databricks auth login --host {workspace} --profile {profile}"
)
raise RuntimeError(
f"Databricks CLI returned no access token for {workspace}. "
Expand Down
2 changes: 1 addition & 1 deletion src/ucode/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def prompt_for_workspace(
choice = questionary.select(
"Select workspace:", choices=choices, style=style, pointer="›", qmark=""
).ask()
if choice is not None:
if isinstance(choice, tuple):
host, profile_name = choice
return normalize_workspace_url(host), profile_name

Expand Down
48 changes: 48 additions & 0 deletions tests/test_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
from __future__ import annotations

from datetime import timedelta
from unittest.mock import patch

import pytest

from ucode.ui import (
format_duration,
format_token_count,
normalize_workspace_url,
prompt_for_workspace,
render_box_table,
status_badge,
)
Expand Down Expand Up @@ -144,3 +146,49 @@ def test_cell_wraps_when_max_width_set(self):
def test_dash_for_empty_cell(self):
result = render_box_table(["A"], [[""]])
assert "-" in result


class TestPromptForWorkspace:
"""Cover the three things `questionary.select(...).ask()` can return:
a (host, profile) tuple, None (cancel or "Enter a different URL"),
or — in some questionary versions — the choice's title string."""

PROFILES = [("https://a.databricks.com", "prof-a"), ("https://b.databricks.com", "prof-b")]

def test_returns_selected_profile_tuple(self):
with patch("ucode.ui.questionary.select") as mock_select:
mock_select.return_value.ask.return_value = (
"https://a.databricks.com",
"prof-a",
)
url, profile = prompt_for_workspace("desc", profiles=self.PROFILES)
assert url == "https://a.databricks.com"
assert profile == "prof-a"

def test_none_falls_through_to_manual_prompt(self):
with (
patch("ucode.ui.questionary.select") as mock_select,
patch("ucode.ui.console.input", return_value="https://manual.databricks.com"),
):
mock_select.return_value.ask.return_value = None
url, profile = prompt_for_workspace("desc", profiles=self.PROFILES)
assert url == "https://manual.databricks.com"
assert profile is None

def test_string_value_falls_through_to_manual_prompt(self):
# Regression: if questionary returns the choice title (e.g. "Enter a
# different URL") instead of its value, we must not try to unpack it.
with (
patch("ucode.ui.questionary.select") as mock_select,
patch("ucode.ui.console.input", return_value="https://manual.databricks.com"),
):
mock_select.return_value.ask.return_value = "Enter a different URL"
url, profile = prompt_for_workspace("desc", profiles=self.PROFILES)
assert url == "https://manual.databricks.com"
assert profile is None

def test_no_profiles_goes_straight_to_manual_prompt(self):
with patch("ucode.ui.console.input", return_value="example.databricks.com"):
url, profile = prompt_for_workspace("desc", profiles=None)
assert url == "https://example.databricks.com"
assert profile is None
Loading