-
Notifications
You must be signed in to change notification settings - Fork 404
test(tools): add unit tests for Bitbucket tools #1017
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| """Tests for BitbucketCommitsTool (function-based, @tool decorated).""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any, cast | ||
| from unittest.mock import patch | ||
|
|
||
| import pytest | ||
|
|
||
| from app.tools.BitbucketCommitsTool import list_bitbucket_commits | ||
| from tests.tools.conftest import BaseToolContract | ||
|
|
||
|
|
||
| def _registered_tool() -> Any: | ||
| return cast(Any, list_bitbucket_commits).__opensre_registered_tool__ | ||
|
|
||
|
|
||
| class TestBitbucketCommitsToolContract(BaseToolContract): | ||
| def get_tool_under_test(self): | ||
| return _registered_tool() | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "sources,expected", | ||
| [ | ||
| ( | ||
| { | ||
| "bitbucket": { | ||
| "connection_verified": True, | ||
| "workspace": "acme", | ||
| "username": "bb-user", | ||
| "app_password": "bb-pass", | ||
| "repo_slug": "backend-service", | ||
| } | ||
| }, | ||
| True, | ||
| ), | ||
| ({"bitbucket": {"connection_verified": True, "workspace": "acme"}}, False), | ||
| ({"bitbucket": {"connection_verified": True, "username": "bb-user"}}, False), | ||
| ({"bitbucket": {"connection_verified": True, "app_password": "bb-pass"}}, False), | ||
| ( | ||
| {"bitbucket": {"workspace": "acme", "username": "bb-user", "app_password": "bb-pass"}}, | ||
| False, | ||
| ), | ||
| ({}, False), | ||
| ], | ||
| ) | ||
| def test_is_available_requires_repo_and_credentials(sources: dict, expected: bool) -> None: | ||
| rt = _registered_tool() | ||
| assert rt.is_available(sources) is expected | ||
|
|
||
|
|
||
| def test_extract_params_maps_fields() -> None: | ||
| rt = _registered_tool() | ||
| params = rt.extract_params( | ||
| { | ||
| "bitbucket": { | ||
| "repo_slug": "backend-service", | ||
| "path": "src/main.py", | ||
| "workspace": "acme", | ||
| "username": "bb-user", | ||
| "app_password": "bb-pass", | ||
| } | ||
| } | ||
| ) | ||
|
|
||
| assert params["repo_slug"] == "backend-service" | ||
| assert params["path"] == "src/main.py" | ||
| assert params["workspace"] == "acme" | ||
| assert params["username"] == "bb-user" | ||
| assert params["app_password"] == "bb-pass" | ||
| assert params["limit"] == 20 | ||
|
|
||
|
|
||
| def test_run_happy_path() -> None: | ||
| mock_result: dict[str, Any] = { | ||
| "source": "bitbucket", | ||
| "available": True, | ||
| "repo": "acme/backend-service", | ||
| "total_returned": 1, | ||
| "commits": [ | ||
| { | ||
| "hash": "abc123def456", | ||
| "message": "Fix flaky test", | ||
| "author": "Jane Doe", | ||
| "date": "2026-04-28T10:00:00Z", | ||
| } | ||
| ], | ||
| } | ||
|
|
||
| with patch( | ||
| "app.tools.BitbucketCommitsTool.list_commits", return_value=mock_result | ||
| ) as mocked_list_commits: | ||
| result = list_bitbucket_commits( | ||
| repo_slug="backend-service", | ||
| workspace="acme", | ||
| username="bb-user", | ||
| app_password="bb-pass", | ||
| path="src/main.py", | ||
| limit=5, | ||
| ) | ||
|
|
||
| assert result == mock_result | ||
| mocked_list_commits.assert_called_once() | ||
| config = mocked_list_commits.call_args.args[0] | ||
| assert config.workspace == "acme" | ||
| assert config.username == "bb-user" | ||
| assert config.app_password == "bb-pass" | ||
| assert mocked_list_commits.call_args.kwargs == { | ||
| "repo_slug": "backend-service", | ||
| "path": "src/main.py", | ||
| "limit": 5, | ||
| } | ||
|
|
||
|
|
||
| def test_run_returns_unavailable_without_credentials() -> None: | ||
| # Ensure env-based config doesn't make this test flaky | ||
| with patch("app.tools.BitbucketSearchCodeTool.bitbucket_config_from_env", return_value=None): | ||
| result = list_bitbucket_commits(repo_slug="backend-service") | ||
|
|
||
| assert result["available"] is False | ||
| assert result["commits"] == [] | ||
| assert result["error"] == "Bitbucket integration is not configured." | ||
|
Comment on lines
+116
to
+123
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| """Tests for BitbucketFileContentsTool (function-based, @tool decorated).""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any, cast | ||
| from unittest.mock import patch | ||
|
|
||
| import pytest | ||
|
|
||
| from app.tools.BitbucketFileContentsTool import get_bitbucket_file_contents | ||
| from tests.tools.conftest import BaseToolContract | ||
|
|
||
|
|
||
| def _registered_tool() -> Any: | ||
| return cast(Any, get_bitbucket_file_contents).__opensre_registered_tool__ | ||
|
|
||
|
|
||
| class TestBitbucketFileContentsToolContract(BaseToolContract): | ||
| def get_tool_under_test(self): | ||
| return _registered_tool() | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "sources,expected", | ||
| [ | ||
| ( | ||
| { | ||
| "bitbucket": { | ||
| "connection_verified": True, | ||
| "workspace": "acme", | ||
| "username": "bb-user", | ||
| "app_password": "bb-pass", | ||
| "repo_slug": "backend-service", | ||
| "path": "src/main.py", | ||
| } | ||
| }, | ||
| True, | ||
| ), | ||
| ( | ||
| { | ||
| "bitbucket": { | ||
| "connection_verified": True, | ||
| "workspace": "acme", | ||
| "username": "bb-user", | ||
| "app_password": "bb-pass", | ||
| } | ||
| }, | ||
| False, | ||
| ), | ||
| ( | ||
| { | ||
| "bitbucket": { | ||
| "connection_verified": True, | ||
| "workspace": "acme", | ||
| "username": "bb-user", | ||
| "app_password": "bb-pass", | ||
| "path": "src/main.py", | ||
| } | ||
| }, | ||
| False, | ||
| ), | ||
| ({"bitbucket": {"connection_verified": True, "workspace": "acme"}}, False), | ||
| ({"bitbucket": {"connection_verified": True, "repo_slug": "backend-service"}}, False), | ||
| ({"bitbucket": {"connection_verified": True, "path": "src/main.py"}}, False), | ||
| ({}, False), | ||
| ], | ||
| ) | ||
| def test_is_available_requires_file_and_credentials(sources: dict, expected: bool) -> None: | ||
| rt = _registered_tool() | ||
| assert rt.is_available(sources) is expected | ||
|
Comment on lines
+23
to
+70
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
|
|
||
| def test_extract_params_maps_fields() -> None: | ||
| rt = _registered_tool() | ||
| params = rt.extract_params( | ||
| { | ||
| "bitbucket": { | ||
| "repo_slug": "backend-service", | ||
| "path": "src/main.py", | ||
| "ref": "main", | ||
| "workspace": "acme", | ||
| "username": "bb-user", | ||
| "app_password": "bb-pass", | ||
| } | ||
| } | ||
| ) | ||
|
|
||
| assert params["repo_slug"] == "backend-service" | ||
| assert params["path"] == "src/main.py" | ||
| assert params["ref"] == "main" | ||
| assert params["workspace"] == "acme" | ||
| assert params["username"] == "bb-user" | ||
| assert params["app_password"] == "bb-pass" | ||
|
|
||
|
|
||
| def test_run_happy_path() -> None: | ||
| mock_result: dict[str, Any] = { | ||
| "source": "bitbucket", | ||
| "available": True, | ||
| "repo": "acme/backend-service", | ||
| "path": "src/main.py", | ||
| "ref": "main", | ||
| "content": "print('hello')", | ||
| "truncated": False, | ||
| } | ||
|
|
||
| with patch( | ||
| "app.tools.BitbucketFileContentsTool.get_file_contents", | ||
| return_value=mock_result, | ||
| ) as mocked_get_file_contents: | ||
| result = get_bitbucket_file_contents( | ||
| repo_slug="backend-service", | ||
| path="src/main.py", | ||
| workspace="acme", | ||
| username="bb-user", | ||
| app_password="bb-pass", | ||
| ref="main", | ||
| ) | ||
|
|
||
| assert result == mock_result | ||
| mocked_get_file_contents.assert_called_once() | ||
| config = mocked_get_file_contents.call_args.args[0] | ||
| assert config.workspace == "acme" | ||
| assert config.username == "bb-user" | ||
| assert config.app_password == "bb-pass" | ||
| assert mocked_get_file_contents.call_args.kwargs == { | ||
| "repo_slug": "backend-service", | ||
| "path": "src/main.py", | ||
| "ref": "main", | ||
| } | ||
|
|
||
|
|
||
| def test_run_returns_unavailable_without_credentials() -> None: | ||
| # Prevent loading real env config in CI/local runs | ||
| with patch("app.tools.BitbucketSearchCodeTool.bitbucket_config_from_env", return_value=None): | ||
| result = get_bitbucket_file_contents(repo_slug="backend-service", path="src/main.py") | ||
|
|
||
| assert result["available"] is False | ||
| assert result["file"] == {} | ||
| assert result["error"] == "Bitbucket integration is not configured." | ||
|
Comment on lines
+133
to
+140
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_availablenegative case for absentrepo_slug_list_bitbucket_commits_availablerequires both the four credentials and arepo_slug(orrepo) key. The parametrized matrix has no case where all four credentials are fully supplied butrepo_slugis omitted — so the per-field credential tests don't prove that therepo_slugguard works independently of the credential guards. Adding a case like{"bitbucket": {"connection_verified": True, "workspace": "acme", "username": "bb-user", "app_password": "bb-pass"}}→Falsewould complete the coverage.