diff --git a/src/mcp_atlassian/confluence/config.py b/src/mcp_atlassian/confluence/config.py index 37d5f7c4b..fac27323f 100644 --- a/src/mcp_atlassian/confluence/config.py +++ b/src/mcp_atlassian/confluence/config.py @@ -90,8 +90,12 @@ def from_env(cls) -> "ConfluenceConfig": oauth_config = get_oauth_config_from_env() auth_type = None - # Use the shared utility function directly - is_cloud = is_atlassian_cloud_url(url) + if url: + is_cloud = is_atlassian_cloud_url(url) + else: + # When no URL, let the @property is_cloud method handle detection + # It will check for OAuth cloud_id or return False + is_cloud = False if oauth_config: # OAuth is available - could be full config or minimal config for user-provided tokens diff --git a/src/mcp_atlassian/jira/config.py b/src/mcp_atlassian/jira/config.py index 323e89a07..415be0118 100644 --- a/src/mcp_atlassian/jira/config.py +++ b/src/mcp_atlassian/jira/config.py @@ -90,8 +90,12 @@ def from_env(cls) -> "JiraConfig": oauth_config = get_oauth_config_from_env() auth_type = None - # Use the shared utility function directly - is_cloud = is_atlassian_cloud_url(url) + if url: + is_cloud = is_atlassian_cloud_url(url) + else: + # When no URL, let the @property is_cloud method handle detection + # It will check for OAuth cloud_id or return False + is_cloud = False if oauth_config: # OAuth is available - could be full config or minimal config for user-provided tokens diff --git a/src/mcp_atlassian/utils/environment.py b/src/mcp_atlassian/utils/environment.py index 0a1799a62..a4c3b2b93 100644 --- a/src/mcp_atlassian/utils/environment.py +++ b/src/mcp_atlassian/utils/environment.py @@ -59,7 +59,8 @@ def get_available_services() -> dict[str, bool | None]: logger.info( "Using Confluence Server/Data Center authentication (PAT or Basic Auth)" ) - elif os.getenv("ATLASSIAN_OAUTH_ENABLE", "").lower() in ("true", "1", "yes"): + + if os.getenv("ATLASSIAN_OAUTH_ENABLE", "").lower() in ("true", "1", "yes"): confluence_is_setup = True logger.info( "Using Confluence minimal OAuth configuration - expecting user-provided tokens via headers" @@ -112,7 +113,8 @@ def get_available_services() -> dict[str, bool | None]: logger.info( "Using Jira Server/Data Center authentication (PAT or Basic Auth)" ) - elif os.getenv("ATLASSIAN_OAUTH_ENABLE", "").lower() in ("true", "1", "yes"): + + if os.getenv("ATLASSIAN_OAUTH_ENABLE", "").lower() in ("true", "1", "yes"): jira_is_setup = True logger.info( "Using Jira minimal OAuth configuration - expecting user-provided tokens via headers" diff --git a/tests/unit/confluence/test_config.py b/tests/unit/confluence/test_config.py index 6e7fd3010..eb7f71f99 100644 --- a/tests/unit/confluence/test_config.py +++ b/tests/unit/confluence/test_config.py @@ -183,3 +183,67 @@ def test_is_cloud_oauth_with_cloud_id(): oauth_config=oauth_config, ) assert config.is_cloud is True + + +def test_from_env_oauth_enable_no_url(): + """Test BYOT OAuth mode - ATLASSIAN_OAUTH_ENABLE=true without URL or cloud_id.""" + with patch.dict( + os.environ, + { + "ATLASSIAN_OAUTH_ENABLE": "true", + # No CONFLUENCE_URL set + # No ATLASSIAN_OAUTH_CLOUD_ID set + }, + clear=True, + ): + config = ConfluenceConfig.from_env() + assert config.auth_type == "oauth" + assert config.is_cloud is False + + +def test_from_env_oauth_enable_no_url_with_cloud_id(): + """Test BYOT OAuth mode - ATLASSIAN_OAUTH_ENABLE=true without URL but with cloud_id.""" + with patch.dict( + os.environ, + { + "ATLASSIAN_OAUTH_ENABLE": "true", + "ATLASSIAN_OAUTH_CLOUD_ID": "test-cloud-id", + # No CONFLUENCE_URL set + }, + clear=True, + ): + config = ConfluenceConfig.from_env() + assert config.auth_type == "oauth" + assert config.is_cloud is True + + +def test_from_env_oauth_enable_with_cloud_url(): + """Test BYOT OAuth mode - ATLASSIAN_OAUTH_ENABLE=true with Cloud URL.""" + with patch.dict( + os.environ, + { + "ATLASSIAN_OAUTH_ENABLE": "true", + "CONFLUENCE_URL": "https://test.atlassian.net/wiki", + }, + clear=True, + ): + config = ConfluenceConfig.from_env() + assert config.url == "https://test.atlassian.net/wiki" + assert config.auth_type == "oauth" + assert config.is_cloud is True # Should be Cloud based on URL + + +def test_from_env_oauth_enable_with_server_url(): + """Test BYOT OAuth mode - ATLASSIAN_OAUTH_ENABLE=true with Server URL.""" + with patch.dict( + os.environ, + { + "ATLASSIAN_OAUTH_ENABLE": "true", + "CONFLUENCE_URL": "https://confluence.example.com", + }, + clear=True, + ): + config = ConfluenceConfig.from_env() + assert config.url == "https://confluence.example.com" + assert config.auth_type == "oauth" + assert config.is_cloud is False # Should be Server based on URL diff --git a/tests/unit/jira/test_config.py b/tests/unit/jira/test_config.py index a0c2815d8..a47359d63 100644 --- a/tests/unit/jira/test_config.py +++ b/tests/unit/jira/test_config.py @@ -203,3 +203,67 @@ def test_is_cloud_oauth_with_cloud_id(): oauth_config=oauth_config, ) assert config.is_cloud is True + + +def test_from_env_oauth_enable_no_url(): + """Test BYOT OAuth mode - ATLASSIAN_OAUTH_ENABLE=true without URL or cloud_id.""" + with patch.dict( + os.environ, + { + "ATLASSIAN_OAUTH_ENABLE": "true", + # No JIRA_URL set + # No ATLASSIAN_OAUTH_CLOUD_ID set + }, + clear=True, + ): + config = JiraConfig.from_env() + assert config.auth_type == "oauth" + assert config.is_cloud is False + + +def test_from_env_oauth_enable_no_url_with_cloud_id(): + """Test BYOT OAuth mode - ATLASSIAN_OAUTH_ENABLE=true without URL but with cloud_id.""" + with patch.dict( + os.environ, + { + "ATLASSIAN_OAUTH_ENABLE": "true", + "ATLASSIAN_OAUTH_CLOUD_ID": "test-cloud-id", + # No JIRA_URL set + }, + clear=True, + ): + config = JiraConfig.from_env() + assert config.auth_type == "oauth" + assert config.is_cloud is True + + +def test_from_env_oauth_enable_with_cloud_url(): + """Test BYOT OAuth mode - ATLASSIAN_OAUTH_ENABLE=true with Cloud URL.""" + with patch.dict( + os.environ, + { + "ATLASSIAN_OAUTH_ENABLE": "true", + "JIRA_URL": "https://test.atlassian.net", + }, + clear=True, + ): + config = JiraConfig.from_env() + assert config.url == "https://test.atlassian.net" + assert config.auth_type == "oauth" + assert config.is_cloud is True + + +def test_from_env_oauth_enable_with_server_url(): + """Test BYOT OAuth mode - ATLASSIAN_OAUTH_ENABLE=true with Server URL.""" + with patch.dict( + os.environ, + { + "ATLASSIAN_OAUTH_ENABLE": "true", + "JIRA_URL": "https://jira.example.com", + }, + clear=True, + ): + config = JiraConfig.from_env() + assert config.url == "https://jira.example.com" + assert config.auth_type == "oauth" + assert config.is_cloud is False diff --git a/tests/unit/utils/test_environment.py b/tests/unit/utils/test_environment.py index 766b11fa6..39acc0aa7 100644 --- a/tests/unit/utils/test_environment.py +++ b/tests/unit/utils/test_environment.py @@ -261,3 +261,84 @@ def test_invalid_environment_variables(self, invalid_vars, caplog): _assert_authentication_logs( caplog, "not_configured", ["confluence", "jira"] ) + + def test_oauth_enable_without_urls(self, caplog): + """Test BYOT OAuth mode - ATLASSIAN_OAUTH_ENABLE=true without service URLs.""" + with MockEnvironment.clean_env(): + import os + + os.environ["ATLASSIAN_OAUTH_ENABLE"] = "true" + + result = get_available_services() + _assert_service_availability( + result, confluence_expected=True, jira_expected=True + ) + # Should log the minimal OAuth configuration messages + assert_log_contains( + caplog, + "INFO", + "Using Confluence minimal OAuth configuration - expecting user-provided tokens via headers", + ) + assert_log_contains( + caplog, + "INFO", + "Using Jira minimal OAuth configuration - expecting user-provided tokens via headers", + ) + + def test_oauth_enable_with_urls(self, caplog): + """Test BYOT OAuth mode - ATLASSIAN_OAUTH_ENABLE=true with service URLs.""" + with MockEnvironment.clean_env(): + import os + + os.environ["ATLASSIAN_OAUTH_ENABLE"] = "true" + os.environ["CONFLUENCE_URL"] = "https://test.atlassian.net/wiki" + os.environ["JIRA_URL"] = "https://test.atlassian.net" + + result = get_available_services() + _assert_service_availability( + result, confluence_expected=True, jira_expected=True + ) + # Should log the minimal OAuth configuration messages (overrides URL-based detection) + assert_log_contains( + caplog, + "INFO", + "Using Confluence minimal OAuth configuration - expecting user-provided tokens via headers", + ) + assert_log_contains( + caplog, + "INFO", + "Using Jira minimal OAuth configuration - expecting user-provided tokens via headers", + ) + + @pytest.mark.parametrize( + "oauth_enable_value", ["true", "True", "TRUE", "1", "yes", "YES"] + ) + def test_oauth_enable_value_variations(self, oauth_enable_value, caplog): + """Test various ATLASSIAN_OAUTH_ENABLE value formats.""" + with MockEnvironment.clean_env(): + import os + + os.environ["ATLASSIAN_OAUTH_ENABLE"] = oauth_enable_value + + result = get_available_services() + _assert_service_availability( + result, confluence_expected=True, jira_expected=True + ) + + @pytest.mark.parametrize( + "oauth_disable_value", ["false", "False", "FALSE", "0", "no", "NO", ""] + ) + def test_oauth_enable_disabled_values(self, oauth_disable_value, caplog): + """Test values that should NOT enable BYOT OAuth mode.""" + with MockEnvironment.clean_env(): + import os + + os.environ["ATLASSIAN_OAUTH_ENABLE"] = oauth_disable_value + + result = get_available_services() + _assert_service_availability( + result, confluence_expected=False, jira_expected=False + ) + _assert_authentication_logs( + caplog, "not_configured", ["confluence", "jira"] + )