diff --git a/README.md b/README.md index 50f2481..e10a595 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ client = Client( ) ``` +`access_token` accepts both personal access tokens (`ldpat_…`) and service +account tokens (`ldsvc_…`); the SDK selects the correct authentication scheme +automatically. + ## Development ### Prerequisites diff --git a/lightdash/client.py b/lightdash/client.py index 2e44768..09d65a2 100644 --- a/lightdash/client.py +++ b/lightdash/client.py @@ -33,6 +33,7 @@ def __init__(self, instance_url: str, access_token: str, project_uuid: str, conf self.instance_url = instance_url.rstrip('/') self.access_token = access_token self.project_uuid = project_uuid + self.auth_header = "Bearer" if access_token.startswith("ldsvc_") else "ApiKey" # Extract config values with defaults config = config or {} @@ -88,7 +89,7 @@ def _make_request( with httpx.Client( headers={ - "Authorization": f"ApiKey {self.access_token}", + "Authorization": f"{self.auth_header} {self.access_token}", "Accept": "application/json", }, timeout=self.timeout diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 0000000..f749849 --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,38 @@ +""" +Unit tests for Client construction. + +Covers the Authorization scheme selection: service account tokens (``ldsvc_``) +authenticate with ``Bearer`` while personal access tokens and anything else use +``ApiKey`` — matching the Lightdash backend auth middleware (issue #17). +""" + +import pytest +from lightdash import Client + + +def _client(token: str) -> Client: + # Construction makes no network call, so a dummy URL/project is fine. + return Client( + instance_url="https://example.lightdash.cloud", + access_token=token, + project_uuid="00000000-0000-0000-0000-000000000000", + ) + + +class TestAuthHeaderScheme: + def test_service_account_token_uses_bearer(self): + """ldsvc_ tokens must use the Bearer scheme.""" + assert _client("ldsvc_abc123").auth_header == "Bearer" + + def test_personal_access_token_uses_apikey(self): + """ldpat_ tokens keep the ApiKey scheme (unchanged behaviour).""" + assert _client("ldpat_abc123").auth_header == "ApiKey" + + def test_unprefixed_token_defaults_to_apikey(self): + """Any other token defaults to ApiKey, preserving backwards compatibility.""" + assert _client("legacy-token").auth_header == "ApiKey" + + def test_authorization_header_value(self): + """The composed Authorization header uses the selected scheme.""" + c = _client("ldsvc_abc123") + assert f"{c.auth_header} {c.access_token}" == "Bearer ldsvc_abc123"