diff --git a/limacharlie/sdk/cases.py b/limacharlie/sdk/cases.py index 9487aa9..b987074 100644 --- a/limacharlie/sdk/cases.py +++ b/limacharlie/sdk/cases.py @@ -24,14 +24,39 @@ class Cases: def __init__(self, org: Organization, api_root: str | None = None) -> None: self._org = org - self._api_root = api_root or os.environ.get( - "LC_CASES_API_ROOT", _DEFAULT_API_ROOT + self._api_root_override = api_root or os.environ.get( + "LC_CASES_API_ROOT", None ) + self._resolved_root: str | None = None @property def oid(self) -> str: return self._org.oid + @property + def _api_root(self) -> str: + """Lazily resolve the cases API root URL. + + Priority: explicit api_root / LC_CASES_API_ROOT env var > + org URLs endpoint ("cases" key) > _DEFAULT_API_ROOT fallback. + """ + if self._api_root_override is not None: + return self._api_root_override + if self._resolved_root is not None: + return self._resolved_root + try: + urls = self._org.get_urls() + cases_url = urls.get("cases", "") + if cases_url: + if not cases_url.startswith("http://") and not cases_url.startswith("https://"): + cases_url = "https://" + cases_url + self._resolved_root = cases_url + else: + self._resolved_root = _DEFAULT_API_ROOT + except Exception: + self._resolved_root = _DEFAULT_API_ROOT + return self._resolved_root + _EXTENSION_NAME = "ext-cases" def create_case( diff --git a/tests/unit/test_sdk_cases.py b/tests/unit/test_sdk_cases.py index 01aa717..9a83e82 100644 --- a/tests/unit/test_sdk_cases.py +++ b/tests/unit/test_sdk_cases.py @@ -13,6 +13,7 @@ def mock_org(): org.oid = "test-oid" org.client = MagicMock() org.client._jwt = "fake-jwt-token" + org.get_urls.return_value = {} return org @@ -40,22 +41,53 @@ def _extract_body(mock_org): class TestCasesInit: def test_default_api_root(self, mock_org): + """When no override and get_urls has no 'cases' key, falls back to default.""" + mock_org.get_urls.return_value = {} + t = Cases(mock_org) + assert t._api_root == "https://cases.limacharlie.io" + + def test_org_url_resolved(self, mock_org): + """When get_urls returns a 'cases' key, use it.""" + mock_org.get_urls.return_value = {"cases": "cases.staging.limacharlie.io"} + t = Cases(mock_org) + assert t._api_root == "https://cases.staging.limacharlie.io" + + def test_org_url_with_scheme(self, mock_org): + """When get_urls returns a URL with scheme, don't double-prefix.""" + mock_org.get_urls.return_value = {"cases": "https://cases.staging.limacharlie.io"} + t = Cases(mock_org) + assert t._api_root == "https://cases.staging.limacharlie.io" + + def test_org_url_cached(self, mock_org): + """get_urls should only be called once.""" + mock_org.get_urls.return_value = {"cases": "cases.staging.limacharlie.io"} + t = Cases(mock_org) + _ = t._api_root + _ = t._api_root + mock_org.get_urls.assert_called_once() + + def test_org_url_error_fallback(self, mock_org): + """When get_urls raises, fall back to default.""" + mock_org.get_urls.side_effect = Exception("network error") t = Cases(mock_org) assert t._api_root == "https://cases.limacharlie.io" def test_custom_api_root(self, mock_org): t = Cases(mock_org, api_root="https://custom.example.com") assert t._api_root == "https://custom.example.com" + mock_org.get_urls.assert_not_called() def test_env_override(self, mock_org, monkeypatch): monkeypatch.setenv("LC_CASES_API_ROOT", "https://env.example.com") t = Cases(mock_org) assert t._api_root == "https://env.example.com" + mock_org.get_urls.assert_not_called() def test_explicit_api_root_overrides_env(self, mock_org, monkeypatch): monkeypatch.setenv("LC_CASES_API_ROOT", "https://env.example.com") t = Cases(mock_org, api_root="https://explicit.example.com") assert t._api_root == "https://explicit.example.com" + mock_org.get_urls.assert_not_called() def test_oid_property(self, cases): assert cases.oid == "test-oid"