From bd819a7eee99a1c5adb237c946b658f5a7686ebc Mon Sep 17 00:00:00 2001 From: Fiorella Aguirrezabala Date: Fri, 20 Mar 2026 13:04:53 -0300 Subject: [PATCH 1/4] fix: update scope creation --- cogsol/core/api.py | 12 +++++++++++- cogsol/core/constants.py | 9 +++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/cogsol/core/api.py b/cogsol/core/api.py index eda7098..5872782 100644 --- a/cogsol/core/api.py +++ b/cogsol/core/api.py @@ -14,8 +14,10 @@ from jwt import decode from cogsol.core.constants import ( + AUTH_SCOPE_IDS, get_cognitive_api_base_url, get_content_api_base_url, + get_cogsol_env, ) @@ -133,6 +135,14 @@ def _refresh_bearer_token(self) -> None: client_id = os.environ.get("COGSOL_AUTH_CLIENT_ID") client_secret = os.environ.get("COGSOL_AUTH_SECRET") + cogsol_env = get_cogsol_env() + scope_id = AUTH_SCOPE_IDS.get(cogsol_env) + if scope_id is None: + raise CogSolAPIError( + f"Unknown COGSOL_ENV value: {cogsol_env!r}. " + "Expected one of: development, testing, implantation, production." + ) + if not client_secret: raise CogSolAPIError( "Missing authentication configuration: COGSOL_AUTH_SECRET is not set.\n" @@ -141,7 +151,7 @@ def _refresh_bearer_token(self) -> None: ) authority = "https://pyxiscognitivesweden.b2clogin.com/pyxiscognitivesweden.onmicrosoft.com/B2C_1A_CS_signup_signin_Sweden_MigrationOIDC" - scopes = [f"https://pyxiscognitivesweden.onmicrosoft.com/{client_id}/.default"] + scopes = [f"https://pyxiscognitivesweden.onmicrosoft.com/{scope_id}/.default"] app = msal.ConfidentialClientApplication( client_id, diff --git a/cogsol/core/constants.py b/cogsol/core/constants.py index 3496684..f09e6ed 100644 --- a/cogsol/core/constants.py +++ b/cogsol/core/constants.py @@ -63,6 +63,14 @@ def get_content_api_base_url() -> str: return _get_env_var(COGSOL_CONTENT_API_BASE_VAR) or get_default_content_api_base_url() +AUTH_SCOPE_IDS: Final[dict[str, str]] = { + "development": "02d621e7-40ef-4a46-a110-4214e1234abf", + "testing": "ce62b03a-7eca-4d4a-9c7a-2f1e001fce29", + "implantation": "9efa4bc6-2b2b-4208-8c88-7a218c7061d6", + "production": "92c0d1cc-127b-4ec5-9be4-960c13c7aecc", +} + + __all__ = [ "COGSOL_ENV_VAR", "COGSOL_API_BASE_VAR", @@ -76,4 +84,5 @@ def get_content_api_base_url() -> str: "get_default_content_api_base_url", "get_cognitive_api_base_url", "get_content_api_base_url", + "AUTH_SCOPE_IDS", ] From d1ea7b6facddaf05b687d5230075a60549285541 Mon Sep 17 00:00:00 2001 From: Fiorella Aguirrezabala Date: Fri, 20 Mar 2026 14:05:04 -0300 Subject: [PATCH 2/4] fix: update scope creation - Replace client_id with env-specific scope_id in token URL - Add AUTH_SCOPE_IDS map to constants for all environments - Validate COGSOL_ENV before attempting token acquisition - Fix import order (ruff) and update tests to set COGSOL_ENV Co-Authored-By: Claude Sonnet 4.6 --- cogsol/core/api.py | 2 +- tests/test_api_key_errors.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cogsol/core/api.py b/cogsol/core/api.py index 5872782..db89279 100644 --- a/cogsol/core/api.py +++ b/cogsol/core/api.py @@ -16,8 +16,8 @@ from cogsol.core.constants import ( AUTH_SCOPE_IDS, get_cognitive_api_base_url, - get_content_api_base_url, get_cogsol_env, + get_content_api_base_url, ) diff --git a/tests/test_api_key_errors.py b/tests/test_api_key_errors.py index 461f3ec..2021b96 100644 --- a/tests/test_api_key_errors.py +++ b/tests/test_api_key_errors.py @@ -44,6 +44,7 @@ class TestMissingAuthSecret: def test_raises_cogsol_api_error(self, monkeypatch): monkeypatch.setenv("COGSOL_AUTH_CLIENT_ID", "test-client-id") + monkeypatch.setenv("COGSOL_ENV", "development") monkeypatch.delenv("COGSOL_AUTH_SECRET", raising=False) with pytest.raises(CogSolAPIError): @@ -51,6 +52,7 @@ def test_raises_cogsol_api_error(self, monkeypatch): def test_error_mentions_missing_secret_var(self, monkeypatch): monkeypatch.setenv("COGSOL_AUTH_CLIENT_ID", "test-client-id") + monkeypatch.setenv("COGSOL_ENV", "development") monkeypatch.delenv("COGSOL_AUTH_SECRET", raising=False) with pytest.raises(CogSolAPIError) as exc_info: @@ -60,6 +62,7 @@ def test_error_mentions_missing_secret_var(self, monkeypatch): def test_error_includes_onboarding_url(self, monkeypatch): monkeypatch.setenv("COGSOL_AUTH_CLIENT_ID", "test-client-id") + monkeypatch.setenv("COGSOL_ENV", "development") monkeypatch.delenv("COGSOL_AUTH_SECRET", raising=False) with pytest.raises(CogSolAPIError) as exc_info: @@ -69,6 +72,7 @@ def test_error_includes_onboarding_url(self, monkeypatch): def test_error_mentions_implantation_portal(self, monkeypatch): monkeypatch.setenv("COGSOL_AUTH_CLIENT_ID", "test-client-id") + monkeypatch.setenv("COGSOL_ENV", "development") monkeypatch.delenv("COGSOL_AUTH_SECRET", raising=False) with pytest.raises(CogSolAPIError) as exc_info: @@ -80,6 +84,7 @@ def test_no_error_when_secret_is_present(self, monkeypatch): """With a valid secret the error must NOT be raised (msal call may fail, but that is a different error path).""" monkeypatch.setenv("COGSOL_AUTH_CLIENT_ID", "test-client-id") + monkeypatch.setenv("COGSOL_ENV", "development") monkeypatch.setenv("COGSOL_AUTH_SECRET", "some-secret") # The error about missing secret should not be raised; From 7a53aa63ecead54a46d0eb15de75157c1c358896 Mon Sep 17 00:00:00 2001 From: Fiorella Aguirrezabala Date: Fri, 20 Mar 2026 14:15:03 -0300 Subject: [PATCH 3/4] test: add tests for CogSolClient._refresh_bearer_token with missing or invalid COGSOL_ENV --- tests/test_api_key_errors.py | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_api_key_errors.py b/tests/test_api_key_errors.py index 2021b96..3895733 100644 --- a/tests/test_api_key_errors.py +++ b/tests/test_api_key_errors.py @@ -38,6 +38,45 @@ def _bare_client() -> CogSolClient: # --------------------------------------------------------------------------- +class TestInvalidCogsolEnv: + """_refresh_bearer_token must raise CogSolAPIError when COGSOL_ENV is + missing or set to an unrecognised value.""" + + def test_raises_when_env_not_set(self, monkeypatch): + monkeypatch.setenv("COGSOL_AUTH_CLIENT_ID", "test-client-id") + monkeypatch.delenv("COGSOL_ENV", raising=False) + + with pytest.raises(CogSolAPIError): + _bare_client()._refresh_bearer_token() + + def test_raises_when_env_is_unknown(self, monkeypatch): + monkeypatch.setenv("COGSOL_AUTH_CLIENT_ID", "test-client-id") + monkeypatch.setenv("COGSOL_ENV", "staging") + + with pytest.raises(CogSolAPIError): + _bare_client()._refresh_bearer_token() + + def test_error_mentions_invalid_env_value(self, monkeypatch): + monkeypatch.setenv("COGSOL_AUTH_CLIENT_ID", "test-client-id") + monkeypatch.setenv("COGSOL_ENV", "staging") + + with pytest.raises(CogSolAPIError) as exc_info: + _bare_client()._refresh_bearer_token() + + assert "staging" in str(exc_info.value) + + def test_error_lists_valid_env_values(self, monkeypatch): + monkeypatch.setenv("COGSOL_AUTH_CLIENT_ID", "test-client-id") + monkeypatch.setenv("COGSOL_ENV", "bad-value") + + with pytest.raises(CogSolAPIError) as exc_info: + _bare_client()._refresh_bearer_token() + + error_msg = str(exc_info.value) + for valid_env in ("development", "testing", "implantation", "production"): + assert valid_env in error_msg + + class TestMissingAuthSecret: """_refresh_bearer_token must raise CogSolAPIError with a helpful message when COGSOL_AUTH_CLIENT_ID is set but COGSOL_AUTH_SECRET is missing.""" From f51a7d7fd550bd4f69d45f1f2f4dd105b33065e8 Mon Sep 17 00:00:00 2001 From: Fiorella Aguirrezabala Date: Tue, 24 Mar 2026 15:48:42 -0300 Subject: [PATCH 4/4] fix: refactor auth scope ID resolution and update related tests --- cogsol/core/api.py | 11 ++------ cogsol/core/constants.py | 17 ++++++++++-- tests/test_api_key_errors.py | 52 ++++++++++++++++++------------------ 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/cogsol/core/api.py b/cogsol/core/api.py index db89279..67343f2 100644 --- a/cogsol/core/api.py +++ b/cogsol/core/api.py @@ -14,9 +14,8 @@ from jwt import decode from cogsol.core.constants import ( - AUTH_SCOPE_IDS, + get_auth_scope_id, get_cognitive_api_base_url, - get_cogsol_env, get_content_api_base_url, ) @@ -135,13 +134,7 @@ def _refresh_bearer_token(self) -> None: client_id = os.environ.get("COGSOL_AUTH_CLIENT_ID") client_secret = os.environ.get("COGSOL_AUTH_SECRET") - cogsol_env = get_cogsol_env() - scope_id = AUTH_SCOPE_IDS.get(cogsol_env) - if scope_id is None: - raise CogSolAPIError( - f"Unknown COGSOL_ENV value: {cogsol_env!r}. " - "Expected one of: development, testing, implantation, production." - ) + scope_id = get_auth_scope_id() if not client_secret: raise CogSolAPIError( diff --git a/cogsol/core/constants.py b/cogsol/core/constants.py index f09e6ed..f0f44c8 100644 --- a/cogsol/core/constants.py +++ b/cogsol/core/constants.py @@ -8,6 +8,7 @@ COGSOL_ENV_VAR: Final = "COGSOL_ENV" COGSOL_API_BASE_VAR: Final = "COGSOL_API_BASE" COGSOL_CONTENT_API_BASE_VAR: Final = "COGSOL_CONTENT_API_BASE" +COGSOL_AUTH_SCOPE_ID_VAR: Final = "COGSOL_AUTH_SCOPE_ID" if os.environ.get("COGSOL_AUTH_CLIENT_ID"): _implantation_cognitive_api_url = "https://apis-imp.cogsol.ai/cognitive" @@ -64,13 +65,23 @@ def get_content_api_base_url() -> str: AUTH_SCOPE_IDS: Final[dict[str, str]] = { - "development": "02d621e7-40ef-4a46-a110-4214e1234abf", - "testing": "ce62b03a-7eca-4d4a-9c7a-2f1e001fce29", "implantation": "9efa4bc6-2b2b-4208-8c88-7a218c7061d6", "production": "92c0d1cc-127b-4ec5-9be4-960c13c7aecc", } +def get_default_auth_scope_id() -> str: + """Return the default OAuth scope ID based on COGSOL_ENV.""" + if get_cogsol_env() == "production": + return AUTH_SCOPE_IDS["production"] + return AUTH_SCOPE_IDS["implantation"] + + +def get_auth_scope_id() -> str: + """Resolve the OAuth scope ID using env overrides when present.""" + return _get_env_var(COGSOL_AUTH_SCOPE_ID_VAR) or get_default_auth_scope_id() + + __all__ = [ "COGSOL_ENV_VAR", "COGSOL_API_BASE_VAR", @@ -85,4 +96,6 @@ def get_content_api_base_url() -> str: "get_cognitive_api_base_url", "get_content_api_base_url", "AUTH_SCOPE_IDS", + "get_default_auth_scope_id", + "get_auth_scope_id", ] diff --git a/tests/test_api_key_errors.py b/tests/test_api_key_errors.py index 3895733..0f1ccce 100644 --- a/tests/test_api_key_errors.py +++ b/tests/test_api_key_errors.py @@ -2,6 +2,7 @@ Tests for API key and credential error messages (branch: csp-1666-api-key-error-msg). Covers: +- get_auth_scope_id: scope resolved from COGSOL_ENV, overridable via COGSOL_AUTH_SCOPE_ID. - CogSolClient._refresh_bearer_token: detailed error when COGSOL_AUTH_SECRET is missing. - migrate Command: no-credentials check shows helpful message and returns 1. - importagent Command: no-credentials check shows helpful message and returns 1. @@ -38,43 +39,42 @@ def _bare_client() -> CogSolClient: # --------------------------------------------------------------------------- -class TestInvalidCogsolEnv: - """_refresh_bearer_token must raise CogSolAPIError when COGSOL_ENV is - missing or set to an unrecognised value.""" +class TestAuthScopeIdResolution: + """get_auth_scope_id must return the correct scope based on COGSOL_ENV, + defaulting to the implantation scope for missing or unknown values. + COGSOL_AUTH_SCOPE_ID overrides the derived value when set.""" - def test_raises_when_env_not_set(self, monkeypatch): - monkeypatch.setenv("COGSOL_AUTH_CLIENT_ID", "test-client-id") + def test_returns_implantation_scope_when_env_not_set(self, monkeypatch): monkeypatch.delenv("COGSOL_ENV", raising=False) + monkeypatch.delenv("COGSOL_AUTH_SCOPE_ID", raising=False) - with pytest.raises(CogSolAPIError): - _bare_client()._refresh_bearer_token() + from cogsol.core.constants import AUTH_SCOPE_IDS, get_auth_scope_id - def test_raises_when_env_is_unknown(self, monkeypatch): - monkeypatch.setenv("COGSOL_AUTH_CLIENT_ID", "test-client-id") - monkeypatch.setenv("COGSOL_ENV", "staging") + assert get_auth_scope_id() == AUTH_SCOPE_IDS["implantation"] - with pytest.raises(CogSolAPIError): - _bare_client()._refresh_bearer_token() + def test_returns_implantation_scope_when_env_is_unknown(self, monkeypatch): + monkeypatch.setenv("COGSOL_ENV", "development") + monkeypatch.delenv("COGSOL_AUTH_SCOPE_ID", raising=False) - def test_error_mentions_invalid_env_value(self, monkeypatch): - monkeypatch.setenv("COGSOL_AUTH_CLIENT_ID", "test-client-id") - monkeypatch.setenv("COGSOL_ENV", "staging") + from cogsol.core.constants import AUTH_SCOPE_IDS, get_auth_scope_id - with pytest.raises(CogSolAPIError) as exc_info: - _bare_client()._refresh_bearer_token() + assert get_auth_scope_id() == AUTH_SCOPE_IDS["implantation"] - assert "staging" in str(exc_info.value) + def test_returns_production_scope_when_env_is_production(self, monkeypatch): + monkeypatch.setenv("COGSOL_ENV", "production") + monkeypatch.delenv("COGSOL_AUTH_SCOPE_ID", raising=False) - def test_error_lists_valid_env_values(self, monkeypatch): - monkeypatch.setenv("COGSOL_AUTH_CLIENT_ID", "test-client-id") - monkeypatch.setenv("COGSOL_ENV", "bad-value") + from cogsol.core.constants import AUTH_SCOPE_IDS, get_auth_scope_id - with pytest.raises(CogSolAPIError) as exc_info: - _bare_client()._refresh_bearer_token() + assert get_auth_scope_id() == AUTH_SCOPE_IDS["production"] + + def test_scope_id_env_var_overrides_derived_value(self, monkeypatch): + monkeypatch.setenv("COGSOL_AUTH_SCOPE_ID", "custom-scope-id-override") + monkeypatch.setenv("COGSOL_ENV", "production") + + from cogsol.core.constants import get_auth_scope_id - error_msg = str(exc_info.value) - for valid_env in ("development", "testing", "implantation", "production"): - assert valid_env in error_msg + assert get_auth_scope_id() == "custom-scope-id-override" class TestMissingAuthSecret: