Files
mcphub/tests/test_gitea_plugin.py
airano-ir f203ca88de
Some checks failed
Release / Test before release (push) Has been cancelled
Release / Publish to PyPI (push) Has been cancelled
Release / Publish to Docker Hub (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
feat(v3.12.0): media pipeline, AI image generation, capability probe, companion v2.9.0
Three-month batch sync from internal repo (~80 commits) covering Tracks F.5a, F.7e, F.8, F.17, F.18, F.X.

WordPress media pipeline
- Pillow-based optimization, AI image generation (OpenAI / Stability / Replicate / Google Nano Banana / OpenRouter), chunked + resumable uploads, bulk delete/reassign, idempotent retries.

Capability discovery (F.7e)
- Per-site credential probe + adapters for WordPress / WooCommerce / Gitea, tier-fit unions granted ∪ roles, capability badge UI with HTMX partial re-check, install hint in every companion-unreachable error.

Companion plugin overhaul
- Renamed wordpress-plugin/airano-mcp-seo-bridge → wordpress-plugin/airano-mcp-bridge.
- Eight new endpoints: /capabilities, /bulk-meta, /export, /cache-purge, /transient-flush, /site-health, /audit-hook, /upload-and-attach.
- wp.org Plugin Check pass: i18n, WP_Filesystem, scheme allowlist on audit-hook URL.

Other
- Gitea ergonomics (F.17): batch files, tree, search, compare, releases, fork.
- Opportunistic bcrypt upgrade for legacy SHA-256 admin keys (F.8).
- n8n refactor: structured errors, capability probe, missing tools backfilled.
- Idempotency-Key dedup for AI media upload retries; WP client fast-fails on unreachable sites.

Docs
- README + CLAUDE.md drop the fixed "633 tools" claim. The total grows with each release; per-plugin approximations + dashboard-surfaced counts replace it.
- Tools/Tests badges removed in favour of "Plugins: 10".

Deployment
- PyPI mirror chain, optional BUILD_HTTP_PROXY, Alpine→Yandex apk mirror, Debian-slim Plan-B Dockerfile, mirror.gcr.io variant.

CI
- Black + Ruff clean on Python 3.12; pytest tests/ green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:25:58 +02:00

1141 lines
43 KiB
Python

"""Tests for Gitea Plugin (plugins/gitea/).
Unit tests covering client initialization, tool specifications,
handler delegation, API request building, and health checks.
"""
import json
from unittest.mock import AsyncMock
import pytest
from plugins.gitea.client import GiteaClient
from plugins.gitea.plugin import GiteaPlugin
# --- GiteaClient Tests ---
class TestGiteaClientInit:
"""Test GiteaClient initialization."""
def test_valid_initialization(self):
"""Should initialize with valid credentials."""
client = GiteaClient(site_url="https://gitea.example.com", token="test-token")
assert client.site_url == "https://gitea.example.com"
assert client.api_base == "https://gitea.example.com/api/v1"
assert client.token == "test-token"
assert client.oauth_enabled is False
def test_trailing_slash_stripped(self):
"""Should strip trailing slash from site URL."""
client = GiteaClient(site_url="https://gitea.example.com/", token="tk")
assert client.site_url == "https://gitea.example.com"
assert client.api_base == "https://gitea.example.com/api/v1"
def test_no_token(self):
"""Should allow initialization without token."""
client = GiteaClient(site_url="https://gitea.example.com")
assert client.token is None
def test_oauth_enabled(self):
"""Should store oauth_enabled flag."""
client = GiteaClient(site_url="https://gitea.example.com", token="tk", oauth_enabled=True)
assert client.oauth_enabled is True
def test_logger_created(self):
"""Should create a logger with site URL."""
client = GiteaClient(site_url="https://gitea.example.com", token="tk")
assert client.logger is not None
assert "gitea.example.com" in client.logger.name
class TestGiteaClientHeaders:
"""Test request header generation."""
def test_default_headers_with_token(self):
"""Should include Authorization header when token is set."""
client = GiteaClient(site_url="https://g.com", token="my-token")
headers = client._get_headers()
assert headers["Authorization"] == "token my-token"
assert headers["Content-Type"] == "application/json"
assert headers["accept"] == "application/json"
def test_default_headers_without_token(self):
"""Should not include Authorization header when no token."""
client = GiteaClient(site_url="https://g.com")
headers = client._get_headers()
assert "Authorization" not in headers
assert headers["Content-Type"] == "application/json"
def test_additional_headers_merged(self):
"""Should merge additional headers."""
client = GiteaClient(site_url="https://g.com", token="tk")
headers = client._get_headers(additional_headers={"X-Custom": "val"})
assert headers["X-Custom"] == "val"
assert headers["Authorization"] == "token tk"
def test_additional_headers_override(self):
"""Should allow overriding default headers."""
client = GiteaClient(site_url="https://g.com", token="tk")
headers = client._get_headers(additional_headers={"Content-Type": "text/plain"})
assert headers["Content-Type"] == "text/plain"
# --- GiteaPlugin Tests ---
class TestGiteaPluginInit:
"""Test GiteaPlugin initialization."""
def test_valid_initialization(self):
"""Should initialize with URL config."""
plugin = GiteaPlugin(config={"url": "https://gitea.example.com", "token": "tk"})
assert plugin.client.site_url == "https://gitea.example.com"
assert plugin.client.token == "tk"
def test_plugin_name(self):
"""Should return 'gitea' as plugin name."""
assert GiteaPlugin.get_plugin_name() == "gitea"
def test_required_config_keys(self):
"""Should require 'url' key."""
assert GiteaPlugin.get_required_config_keys() == ["url"]
def test_oauth_config(self):
"""Should pass oauth_enabled to client."""
plugin = GiteaPlugin(config={"url": "https://gitea.example.com", "oauth_enabled": True})
assert plugin.client.oauth_enabled is True
def test_getattr_raises_for_unknown(self):
"""Should raise AttributeError for unknown method."""
plugin = GiteaPlugin(config={"url": "https://gitea.example.com"})
with pytest.raises(AttributeError, match="has no attribute 'nonexistent_method'"):
plugin.nonexistent_method # noqa: B018
# --- Tool Specification Tests ---
class TestGiteaToolSpecifications:
"""Test tool specifications from all handlers."""
@pytest.fixture()
def specs(self):
"""Get all tool specifications."""
return GiteaPlugin.get_tool_specifications()
def test_total_tool_count(self, specs):
"""Gitea tool count.
Base v3.6.0 count was 58. F.17 ergonomics (2026-04-16) added 7
tools: create_files, get_tree, search_code, compare, list_releases,
create_release, fork_repository.
"""
assert len(specs) == 65
def test_all_specs_have_required_keys(self, specs):
"""Every spec must have name, method_name, description, schema, scope."""
required_keys = {"name", "method_name", "description", "schema", "scope"}
for spec in specs:
missing = required_keys - set(spec.keys())
assert not missing, f"Tool '{spec.get('name', '?')}' missing keys: {missing}"
def test_all_specs_have_unique_names(self, specs):
"""All tool names must be unique."""
names = [s["name"] for s in specs]
assert len(names) == len(
set(names)
), f"Duplicate names: {[n for n in names if names.count(n) > 1]}"
def test_all_scopes_are_valid(self, specs):
"""All scopes must be read, write, or admin."""
valid_scopes = {"read", "write", "admin"}
for spec in specs:
assert (
spec["scope"] in valid_scopes
), f"Tool '{spec['name']}' has invalid scope: {spec['scope']}"
def test_all_schemas_have_type_object(self, specs):
"""All schemas must be type: object."""
for spec in specs:
assert spec["schema"]["type"] == "object", f"Tool '{spec['name']}' schema not object"
def test_repository_tools_present(self, specs):
"""Should have all 16 repository tools."""
repo_tools = {
"list_repositories",
"get_repository",
"create_repository",
"update_repository",
"delete_repository",
"list_branches",
"get_branch",
"create_branch",
"delete_branch",
"list_tags",
"create_tag",
"delete_tag",
"get_file",
"create_file",
"update_file",
"delete_file",
}
names = {s["name"] for s in specs}
assert repo_tools.issubset(names), f"Missing repo tools: {repo_tools - names}"
def test_issue_tools_present(self, specs):
"""Should have all 13 issue tools."""
issue_tools = {
"list_issues",
"get_issue",
"create_issue",
"update_issue",
"close_issue",
"reopen_issue",
"list_issue_comments",
"create_issue_comment",
"list_labels",
"create_label",
"delete_label",
"list_milestones",
"create_milestone",
}
names = {s["name"] for s in specs}
assert issue_tools.issubset(names), f"Missing issue tools: {issue_tools - names}"
def test_pull_request_tools_present(self, specs):
"""Should have all 15 pull request tools."""
pr_tools = {
"list_pull_requests",
"get_pull_request",
"create_pull_request",
"update_pull_request",
"merge_pull_request",
"close_pull_request",
"reopen_pull_request",
"list_pr_commits",
"list_pr_files",
"get_pr_diff",
"list_pr_comments",
"create_pr_comment",
"list_pr_reviews",
"create_pr_review",
"request_pr_reviewers",
}
names = {s["name"] for s in specs}
assert pr_tools.issubset(names), f"Missing PR tools: {pr_tools - names}"
def test_user_tools_present(self, specs):
"""Should have all 8 user tools."""
user_tools = {
"get_user",
"list_user_repos",
"search_users",
"list_organizations",
"get_organization",
"list_org_repos",
"list_org_teams",
"list_team_members",
}
names = {s["name"] for s in specs}
assert user_tools.issubset(names), f"Missing user tools: {user_tools - names}"
def test_webhook_tools_present(self, specs):
"""Should have all 6 webhook tools."""
webhook_tools = {
"list_webhooks",
"create_webhook",
"get_webhook",
"update_webhook",
"delete_webhook",
"test_webhook",
}
names = {s["name"] for s in specs}
assert webhook_tools.issubset(names), f"Missing webhook tools: {webhook_tools - names}"
def test_delete_repository_is_admin_scope(self, specs):
"""delete_repository should be admin scope."""
spec = next(s for s in specs if s["name"] == "delete_repository")
assert spec["scope"] == "admin"
def test_list_issues_is_read_scope(self, specs):
"""list_issues should be read scope."""
spec = next(s for s in specs if s["name"] == "list_issues")
assert spec["scope"] == "read"
def test_create_issue_is_write_scope(self, specs):
"""create_issue should be write scope."""
spec = next(s for s in specs if s["name"] == "create_issue")
assert spec["scope"] == "write"
def test_create_webhook_is_admin_scope(self, specs):
"""create_webhook should be admin scope."""
spec = next(s for s in specs if s["name"] == "create_webhook")
assert spec["scope"] == "admin"
def test_update_webhook_is_admin_scope(self, specs):
"""update_webhook should be admin scope."""
spec = next(s for s in specs if s["name"] == "update_webhook")
assert spec["scope"] == "admin"
def test_merge_pr_schema_has_method_enum(self, specs):
"""merge_pull_request should have method enum."""
spec = next(s for s in specs if s["name"] == "merge_pull_request")
method_prop = spec["schema"]["properties"]["method"]
assert method_prop["enum"] == ["merge", "rebase", "rebase-merge", "squash"]
def test_create_issue_required_fields(self, specs):
"""create_issue should require owner, repo, title."""
spec = next(s for s in specs if s["name"] == "create_issue")
assert set(spec["schema"]["required"]) == {"owner", "repo", "title"}
def test_create_pr_required_fields(self, specs):
"""create_pull_request should require owner, repo, title, head, base."""
spec = next(s for s in specs if s["name"] == "create_pull_request")
assert set(spec["schema"]["required"]) == {"owner", "repo", "title", "head", "base"}
# --- Repository Handler Tests ---
class TestRepositoryHandlers:
"""Test repository handler functions via mocked client."""
@pytest.mark.asyncio
async def test_list_repositories(self):
"""Should return list of repositories."""
from plugins.gitea.handlers.repositories import list_repositories
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_repositories = AsyncMock(return_value=[{"name": "repo1"}, {"name": "repo2"}])
result = json.loads(await list_repositories(client, owner="user1"))
assert result["success"] is True
assert result["count"] == 2
assert len(result["repositories"]) == 2
@pytest.mark.asyncio
async def test_get_repository(self):
"""Should return repository details."""
from plugins.gitea.handlers.repositories import get_repository
client = GiteaClient(site_url="https://g.com", token="tk")
client.get_repository = AsyncMock(return_value={"name": "repo1", "full_name": "user/repo1"})
result = json.loads(await get_repository(client, owner="user", repo="repo1"))
assert result["success"] is True
assert result["repository"]["name"] == "repo1"
@pytest.mark.asyncio
async def test_create_repository(self):
"""Should create and return repository."""
from plugins.gitea.handlers.repositories import create_repository
client = GiteaClient(site_url="https://g.com", token="tk")
client.create_repository = AsyncMock(return_value={"name": "new-repo", "id": 42})
result = json.loads(await create_repository(client, name="new-repo", private=True))
assert result["success"] is True
assert "new-repo" in result["message"]
client.create_repository.assert_called_once()
@pytest.mark.asyncio
async def test_create_repository_in_org(self):
"""Should create repo under organization."""
from plugins.gitea.handlers.repositories import create_repository
client = GiteaClient(site_url="https://g.com", token="tk")
client.create_repository = AsyncMock(return_value={"name": "org-repo"})
await create_repository(client, name="org-repo", org="myorg")
call_kwargs = client.create_repository.call_args
assert call_kwargs.kwargs.get("org") == "myorg" or call_kwargs[1].get("org") == "myorg"
@pytest.mark.asyncio
async def test_delete_repository(self):
"""Should delete repository."""
from plugins.gitea.handlers.repositories import delete_repository
client = GiteaClient(site_url="https://g.com", token="tk")
client.delete_repository = AsyncMock(return_value=None)
result = json.loads(await delete_repository(client, owner="user", repo="old-repo"))
assert result["success"] is True
assert "deleted" in result["message"]
@pytest.mark.asyncio
async def test_list_branches(self):
"""Should return branches."""
from plugins.gitea.handlers.repositories import list_branches
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_branches = AsyncMock(return_value=[{"name": "main"}, {"name": "dev"}])
result = json.loads(await list_branches(client, owner="u", repo="r"))
assert result["count"] == 2
@pytest.mark.asyncio
async def test_create_branch(self):
"""Should create branch."""
from plugins.gitea.handlers.repositories import create_branch
client = GiteaClient(site_url="https://g.com", token="tk")
client.create_branch = AsyncMock(return_value={"name": "feature"})
result = json.loads(
await create_branch(client, owner="u", repo="r", new_branch_name="feature")
)
assert result["success"] is True
assert "feature" in result["message"]
@pytest.mark.asyncio
async def test_delete_branch(self):
"""Should delete branch."""
from plugins.gitea.handlers.repositories import delete_branch
client = GiteaClient(site_url="https://g.com", token="tk")
client.delete_branch = AsyncMock(return_value=None)
result = json.loads(await delete_branch(client, owner="u", repo="r", branch="old"))
assert result["success"] is True
@pytest.mark.asyncio
async def test_list_tags(self):
"""Should return tags."""
from plugins.gitea.handlers.repositories import list_tags
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_tags = AsyncMock(return_value=[{"name": "v1.0"}])
result = json.loads(await list_tags(client, owner="u", repo="r"))
assert result["count"] == 1
@pytest.mark.asyncio
async def test_create_tag(self):
"""Should create tag."""
from plugins.gitea.handlers.repositories import create_tag
client = GiteaClient(site_url="https://g.com", token="tk")
client.create_tag = AsyncMock(return_value={"name": "v2.0"})
result = json.loads(await create_tag(client, owner="u", repo="r", tag_name="v2.0"))
assert result["success"] is True
@pytest.mark.asyncio
async def test_get_file(self):
"""Should return file contents."""
from plugins.gitea.handlers.repositories import get_file
client = GiteaClient(site_url="https://g.com", token="tk")
client.get_file = AsyncMock(return_value={"content": "SGVsbG8=", "name": "README.md"})
result = json.loads(await get_file(client, owner="u", repo="r", path="README.md"))
assert result["success"] is True
assert result["file"]["name"] == "README.md"
@pytest.mark.asyncio
async def test_create_file(self):
"""Should create file."""
from plugins.gitea.handlers.repositories import create_file
client = GiteaClient(site_url="https://g.com", token="tk")
client.create_file = AsyncMock(return_value={"content": {"name": "test.txt"}})
result = json.loads(
await create_file(
client, owner="u", repo="r", path="test.txt", content="Hello", message="add"
)
)
assert result["success"] is True
@pytest.mark.asyncio
async def test_update_file(self):
"""Should update file."""
from plugins.gitea.handlers.repositories import update_file
client = GiteaClient(site_url="https://g.com", token="tk")
client.update_file = AsyncMock(return_value={"content": {"name": "test.txt"}})
result = json.loads(
await update_file(
client,
owner="u",
repo="r",
path="test.txt",
content="Updated",
sha="abc123",
message="update",
)
)
assert result["success"] is True
@pytest.mark.asyncio
async def test_delete_file(self):
"""Should delete file."""
from plugins.gitea.handlers.repositories import delete_file
client = GiteaClient(site_url="https://g.com", token="tk")
client.delete_file = AsyncMock(return_value=None)
result = json.loads(
await delete_file(client, owner="u", repo="r", path="old.txt", sha="abc", message="rm")
)
assert result["success"] is True
# --- Issue Handler Tests ---
class TestIssueHandlers:
"""Test issue handler functions via mocked client."""
@pytest.mark.asyncio
async def test_list_issues(self):
"""Should return issues list."""
from plugins.gitea.handlers.issues import list_issues
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_issues = AsyncMock(return_value=[{"number": 1}, {"number": 2}])
result = json.loads(await list_issues(client, owner="u", repo="r"))
assert result["count"] == 2
@pytest.mark.asyncio
async def test_get_issue(self):
"""Should return issue details."""
from plugins.gitea.handlers.issues import get_issue
client = GiteaClient(site_url="https://g.com", token="tk")
client.get_issue = AsyncMock(return_value={"number": 5, "title": "Bug"})
result = json.loads(await get_issue(client, owner="u", repo="r", issue_number=5))
assert result["issue"]["title"] == "Bug"
@pytest.mark.asyncio
async def test_create_issue(self):
"""Should create issue."""
from plugins.gitea.handlers.issues import create_issue
client = GiteaClient(site_url="https://g.com", token="tk")
client.create_issue = AsyncMock(return_value={"number": 10, "title": "New bug"})
result = json.loads(await create_issue(client, owner="u", repo="r", title="New bug"))
assert result["success"] is True
assert "#10" in result["message"]
@pytest.mark.asyncio
async def test_close_issue(self):
"""Should close issue."""
from plugins.gitea.handlers.issues import close_issue
client = GiteaClient(site_url="https://g.com", token="tk")
client.update_issue = AsyncMock(return_value={"number": 5, "state": "closed"})
result = json.loads(await close_issue(client, owner="u", repo="r", issue_number=5))
assert "closed" in result["message"]
client.update_issue.assert_called_once_with("u", "r", 5, {"state": "closed"})
@pytest.mark.asyncio
async def test_reopen_issue(self):
"""Should reopen issue."""
from plugins.gitea.handlers.issues import reopen_issue
client = GiteaClient(site_url="https://g.com", token="tk")
client.update_issue = AsyncMock(return_value={"number": 5, "state": "open"})
result = json.loads(await reopen_issue(client, owner="u", repo="r", issue_number=5))
assert "reopened" in result["message"]
client.update_issue.assert_called_once_with("u", "r", 5, {"state": "open"})
@pytest.mark.asyncio
async def test_list_issue_comments(self):
"""Should return comments."""
from plugins.gitea.handlers.issues import list_issue_comments
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_issue_comments = AsyncMock(return_value=[{"id": 1, "body": "hi"}])
result = json.loads(await list_issue_comments(client, owner="u", repo="r", issue_number=1))
assert result["count"] == 1
@pytest.mark.asyncio
async def test_create_issue_comment(self):
"""Should create comment."""
from plugins.gitea.handlers.issues import create_issue_comment
client = GiteaClient(site_url="https://g.com", token="tk")
client.create_issue_comment = AsyncMock(return_value={"id": 2, "body": "Thanks"})
result = json.loads(
await create_issue_comment(client, owner="u", repo="r", issue_number=1, body="Thanks")
)
assert result["success"] is True
@pytest.mark.asyncio
async def test_list_labels(self):
"""Should return labels."""
from plugins.gitea.handlers.issues import list_labels
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_labels = AsyncMock(return_value=[{"id": 1, "name": "bug"}])
result = json.loads(await list_labels(client, owner="u", repo="r"))
assert result["count"] == 1
@pytest.mark.asyncio
async def test_create_label(self):
"""Should create label."""
from plugins.gitea.handlers.issues import create_label
client = GiteaClient(site_url="https://g.com", token="tk")
client.create_label = AsyncMock(return_value={"id": 2, "name": "feature"})
result = json.loads(
await create_label(client, owner="u", repo="r", name="feature", color="00ff00")
)
assert result["success"] is True
assert "feature" in result["message"]
@pytest.mark.asyncio
async def test_delete_label(self):
"""Should delete label."""
from plugins.gitea.handlers.issues import delete_label
client = GiteaClient(site_url="https://g.com", token="tk")
client.delete_label = AsyncMock(return_value=None)
result = json.loads(await delete_label(client, owner="u", repo="r", label_id=1))
assert result["success"] is True
assert "deleted" in result["message"]
@pytest.mark.asyncio
async def test_list_milestones(self):
"""Should return milestones."""
from plugins.gitea.handlers.issues import list_milestones
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_milestones = AsyncMock(return_value=[{"id": 1, "title": "v1.0"}])
result = json.loads(await list_milestones(client, owner="u", repo="r"))
assert result["count"] == 1
@pytest.mark.asyncio
async def test_create_milestone(self):
"""Should create milestone."""
from plugins.gitea.handlers.issues import create_milestone
client = GiteaClient(site_url="https://g.com", token="tk")
client.create_milestone = AsyncMock(return_value={"id": 2, "title": "v2.0"})
result = json.loads(await create_milestone(client, owner="u", repo="r", title="v2.0"))
assert result["success"] is True
# --- Pull Request Handler Tests ---
class TestPullRequestHandlers:
"""Test pull request handler functions via mocked client."""
@pytest.mark.asyncio
async def test_list_pull_requests(self):
"""Should return pull requests."""
from plugins.gitea.handlers.pull_requests import list_pull_requests
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_pull_requests = AsyncMock(return_value=[{"number": 1}])
result = json.loads(await list_pull_requests(client, owner="u", repo="r"))
assert result["count"] == 1
@pytest.mark.asyncio
async def test_get_pull_request(self):
"""Should return PR details."""
from plugins.gitea.handlers.pull_requests import get_pull_request
client = GiteaClient(site_url="https://g.com", token="tk")
client.get_pull_request = AsyncMock(return_value={"number": 3, "title": "Fix"})
result = json.loads(await get_pull_request(client, owner="u", repo="r", pr_number=3))
assert result["pull_request"]["title"] == "Fix"
@pytest.mark.asyncio
async def test_create_pull_request(self):
"""Should create PR."""
from plugins.gitea.handlers.pull_requests import create_pull_request
client = GiteaClient(site_url="https://g.com", token="tk")
client.create_pull_request = AsyncMock(return_value={"number": 7, "title": "New feature"})
result = json.loads(
await create_pull_request(
client, owner="u", repo="r", title="New feature", head="feat", base="main"
)
)
assert result["success"] is True
assert "#7" in result["message"]
@pytest.mark.asyncio
async def test_merge_pull_request(self):
"""Should merge PR with specified method."""
from plugins.gitea.handlers.pull_requests import merge_pull_request
client = GiteaClient(site_url="https://g.com", token="tk")
client.merge_pull_request = AsyncMock(return_value={"merged": True})
result = json.loads(
await merge_pull_request(client, owner="u", repo="r", pr_number=3, method="squash")
)
assert result["success"] is True
assert "squash" in result["message"]
@pytest.mark.asyncio
async def test_close_pull_request(self):
"""Should close PR."""
from plugins.gitea.handlers.pull_requests import close_pull_request
client = GiteaClient(site_url="https://g.com", token="tk")
client.update_pull_request = AsyncMock(return_value={"number": 3, "state": "closed"})
result = json.loads(await close_pull_request(client, owner="u", repo="r", pr_number=3))
assert "closed" in result["message"]
@pytest.mark.asyncio
async def test_reopen_pull_request(self):
"""Should reopen PR."""
from plugins.gitea.handlers.pull_requests import reopen_pull_request
client = GiteaClient(site_url="https://g.com", token="tk")
client.update_pull_request = AsyncMock(return_value={"number": 3, "state": "open"})
result = json.loads(await reopen_pull_request(client, owner="u", repo="r", pr_number=3))
assert "reopened" in result["message"]
@pytest.mark.asyncio
async def test_list_pr_commits(self):
"""Should return PR commits."""
from plugins.gitea.handlers.pull_requests import list_pr_commits
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_pr_commits = AsyncMock(return_value=[{"sha": "abc"}])
result = json.loads(await list_pr_commits(client, owner="u", repo="r", pr_number=1))
assert result["count"] == 1
@pytest.mark.asyncio
async def test_list_pr_files(self):
"""Should return PR files."""
from plugins.gitea.handlers.pull_requests import list_pr_files
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_pr_files = AsyncMock(return_value=[{"filename": "a.py"}])
result = json.loads(await list_pr_files(client, owner="u", repo="r", pr_number=1))
assert result["count"] == 1
@pytest.mark.asyncio
async def test_get_pr_diff(self):
"""Should return PR diff."""
from plugins.gitea.handlers.pull_requests import get_pr_diff
client = GiteaClient(site_url="https://g.com", token="tk")
client.get_pr_diff = AsyncMock(return_value="--- a/file\n+++ b/file")
result = json.loads(await get_pr_diff(client, owner="u", repo="r", pr_number=1))
assert result["success"] is True
@pytest.mark.asyncio
async def test_create_pr_comment(self):
"""Should create PR comment."""
from plugins.gitea.handlers.pull_requests import create_pr_comment
client = GiteaClient(site_url="https://g.com", token="tk")
client.create_issue_comment = AsyncMock(return_value={"id": 5, "body": "LGTM"})
result = json.loads(
await create_pr_comment(client, owner="u", repo="r", pr_number=1, body="LGTM")
)
assert result["success"] is True
@pytest.mark.asyncio
async def test_list_pr_reviews(self):
"""Should return PR reviews."""
from plugins.gitea.handlers.pull_requests import list_pr_reviews
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_pr_reviews = AsyncMock(return_value=[{"id": 1, "state": "APPROVED"}])
result = json.loads(await list_pr_reviews(client, owner="u", repo="r", pr_number=1))
assert result["count"] == 1
@pytest.mark.asyncio
async def test_create_pr_review(self):
"""Should create PR review."""
from plugins.gitea.handlers.pull_requests import create_pr_review
client = GiteaClient(site_url="https://g.com", token="tk")
client.create_pr_review = AsyncMock(return_value={"id": 2, "state": "APPROVED"})
result = json.loads(
await create_pr_review(client, owner="u", repo="r", pr_number=1, event="APPROVED")
)
assert result["success"] is True
@pytest.mark.asyncio
async def test_request_pr_reviewers(self):
"""Should request reviewers."""
from plugins.gitea.handlers.pull_requests import request_pr_reviewers
client = GiteaClient(site_url="https://g.com", token="tk")
client.request_pr_reviewers = AsyncMock(return_value={"ok": True})
result = json.loads(
await request_pr_reviewers(
client, owner="u", repo="r", pr_number=1, reviewers=["alice"]
)
)
assert result["success"] is True
# --- User Handler Tests ---
class TestUserHandlers:
"""Test user handler functions via mocked client."""
@pytest.mark.asyncio
async def test_get_user(self):
"""Should return user info."""
from plugins.gitea.handlers.users import get_user
client = GiteaClient(site_url="https://g.com", token="tk")
client.get_user = AsyncMock(return_value={"login": "alice", "id": 1})
result = json.loads(await get_user(client, username="alice"))
assert result["user"]["login"] == "alice"
@pytest.mark.asyncio
async def test_list_user_repos(self):
"""Should return user repos."""
from plugins.gitea.handlers.users import list_user_repos
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_user_repos = AsyncMock(return_value=[{"name": "r1"}])
result = json.loads(await list_user_repos(client, username="alice"))
assert result["count"] == 1
@pytest.mark.asyncio
async def test_search_users(self):
"""Should search users."""
from plugins.gitea.handlers.users import search_users
client = GiteaClient(site_url="https://g.com", token="tk")
client.search_users = AsyncMock(return_value=[{"login": "bob"}])
result = json.loads(await search_users(client, q="bob"))
assert result["count"] == 1
@pytest.mark.asyncio
async def test_list_organizations(self):
"""Should return orgs."""
from plugins.gitea.handlers.users import list_organizations
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_organizations = AsyncMock(return_value=[{"username": "myorg"}])
result = json.loads(await list_organizations(client))
assert result["count"] == 1
@pytest.mark.asyncio
async def test_get_organization(self):
"""Should return org info."""
from plugins.gitea.handlers.users import get_organization
client = GiteaClient(site_url="https://g.com", token="tk")
client.get_organization = AsyncMock(return_value={"username": "myorg"})
result = json.loads(await get_organization(client, org="myorg"))
assert result["organization"]["username"] == "myorg"
@pytest.mark.asyncio
async def test_list_org_repos(self):
"""Should return org repos."""
from plugins.gitea.handlers.users import list_org_repos
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_org_repos = AsyncMock(return_value=[{"name": "proj1"}])
result = json.loads(await list_org_repos(client, org="myorg"))
assert result["count"] == 1
@pytest.mark.asyncio
async def test_list_org_teams(self):
"""Should return org teams."""
from plugins.gitea.handlers.users import list_org_teams
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_org_teams = AsyncMock(return_value=[{"id": 1, "name": "devs"}])
result = json.loads(await list_org_teams(client, org="myorg"))
assert result["count"] == 1
@pytest.mark.asyncio
async def test_list_team_members(self):
"""Should return team members."""
from plugins.gitea.handlers.users import list_team_members
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_team_members = AsyncMock(return_value=[{"login": "alice"}])
result = json.loads(await list_team_members(client, team_id=1))
assert result["count"] == 1
# --- Webhook Handler Tests ---
class TestWebhookHandlers:
"""Test webhook handler functions via mocked client."""
@pytest.mark.asyncio
async def test_list_webhooks(self):
"""Should return webhooks list."""
from plugins.gitea.handlers.webhooks import list_webhooks
client = GiteaClient(site_url="https://g.com", token="tk")
client.list_webhooks = AsyncMock(
return_value=[{"id": 1, "url": "https://hook.example.com"}]
)
result = json.loads(await list_webhooks(client, owner="u", repo="r"))
assert result["count"] == 1
@pytest.mark.asyncio
async def test_create_webhook(self):
"""Should create webhook."""
from plugins.gitea.handlers.webhooks import create_webhook
client = GiteaClient(site_url="https://g.com", token="tk")
client.create_webhook = AsyncMock(return_value={"id": 2})
result = json.loads(
await create_webhook(
client, owner="u", repo="r", url="https://hook.example.com", events=["push"]
)
)
assert result["success"] is True
@pytest.mark.asyncio
async def test_create_webhook_builds_config(self):
"""Should build correct webhook config."""
from plugins.gitea.handlers.webhooks import create_webhook
client = GiteaClient(site_url="https://g.com", token="tk")
client.create_webhook = AsyncMock(return_value={"id": 3})
await create_webhook(
client,
owner="u",
repo="r",
url="https://hook.example.com",
events=["push", "issues"],
content_type="form",
secret="s3cret",
active=False,
type="slack",
)
call_data = client.create_webhook.call_args[0][2] # data arg
assert call_data["config"]["url"] == "https://hook.example.com"
assert call_data["config"]["content_type"] == "form"
assert call_data["config"]["secret"] == "s3cret"
assert call_data["events"] == ["push", "issues"]
assert call_data["active"] is False
assert call_data["type"] == "slack"
@pytest.mark.asyncio
async def test_get_webhook(self):
"""Should return webhook details."""
from plugins.gitea.handlers.webhooks import get_webhook
client = GiteaClient(site_url="https://g.com", token="tk")
client.get_webhook = AsyncMock(return_value={"id": 1, "active": True})
result = json.loads(await get_webhook(client, owner="u", repo="r", webhook_id=1))
assert result["webhook"]["active"] is True
@pytest.mark.asyncio
async def test_update_webhook(self):
"""Should update webhook."""
from plugins.gitea.handlers.webhooks import update_webhook
client = GiteaClient(site_url="https://g.com", token="tk")
client.update_webhook = AsyncMock(return_value={"id": 1, "active": False})
result = json.loads(
await update_webhook(client, owner="u", repo="r", webhook_id=1, active=False)
)
assert result["success"] is True
assert "updated" in result["message"]
@pytest.mark.asyncio
async def test_update_webhook_builds_data(self):
"""Should build correct update data with config."""
from plugins.gitea.handlers.webhooks import update_webhook
client = GiteaClient(site_url="https://g.com", token="tk")
client.update_webhook = AsyncMock(return_value={"id": 1})
await update_webhook(
client,
owner="u",
repo="r",
webhook_id=1,
url="https://new-hook.com",
events=["push"],
content_type="json",
active=True,
)
call_data = client.update_webhook.call_args[0][3] # data arg
assert call_data["config"]["url"] == "https://new-hook.com"
assert call_data["config"]["content_type"] == "json"
assert call_data["events"] == ["push"]
assert call_data["active"] is True
@pytest.mark.asyncio
async def test_update_webhook_minimal(self):
"""Should send minimal data when only active is changed."""
from plugins.gitea.handlers.webhooks import update_webhook
client = GiteaClient(site_url="https://g.com", token="tk")
client.update_webhook = AsyncMock(return_value={"id": 1})
await update_webhook(client, owner="u", repo="r", webhook_id=1, active=False)
call_data = client.update_webhook.call_args[0][3]
assert call_data == {"active": False}
assert "config" not in call_data
@pytest.mark.asyncio
async def test_delete_webhook(self):
"""Should delete webhook."""
from plugins.gitea.handlers.webhooks import delete_webhook
client = GiteaClient(site_url="https://g.com", token="tk")
client.delete_webhook = AsyncMock(return_value=None)
result = json.loads(await delete_webhook(client, owner="u", repo="r", webhook_id=1))
assert result["success"] is True
assert "deleted" in result["message"]
@pytest.mark.asyncio
async def test_test_webhook(self):
"""Should test webhook."""
from plugins.gitea.handlers.webhooks import test_webhook
client = GiteaClient(site_url="https://g.com", token="tk")
client.test_webhook = AsyncMock(return_value={"status": 200})
result = json.loads(await test_webhook(client, owner="u", repo="r", webhook_id=1))
assert result["success"] is True
# --- Health Check Tests ---
class TestGiteaHealthCheck:
"""Test plugin health check."""
@pytest.mark.asyncio
async def test_health_check_success(self):
"""Should return healthy when API is accessible."""
plugin = GiteaPlugin(config={"url": "https://gitea.example.com", "token": "tk"})
plugin.client.request = AsyncMock(return_value={"login": "admin"})
result = await plugin.health_check()
assert result["healthy"] is True
@pytest.mark.asyncio
async def test_health_check_failure(self):
"""Should return unhealthy on API error."""
plugin = GiteaPlugin(config={"url": "https://gitea.example.com", "token": "tk"})
plugin.client.request = AsyncMock(side_effect=Exception("Connection refused"))
result = await plugin.health_check()
assert result["healthy"] is False
assert "Connection refused" in result["message"]
# --- Plugin Delegation Tests ---
class TestGiteaPluginDelegation:
"""Test that plugin delegates methods to correct handlers."""
@pytest.mark.asyncio
async def test_delegates_to_repositories(self):
"""Should delegate list_repositories to repos handler."""
plugin = GiteaPlugin(config={"url": "https://g.com", "token": "tk"})
plugin.client.list_repositories = AsyncMock(return_value=[])
result = json.loads(await plugin.list_repositories())
assert result["success"] is True
@pytest.mark.asyncio
async def test_delegates_to_issues(self):
"""Should delegate list_issues to issues handler."""
plugin = GiteaPlugin(config={"url": "https://g.com", "token": "tk"})
plugin.client.list_issues = AsyncMock(return_value=[])
result = json.loads(await plugin.list_issues(owner="u", repo="r"))
assert result["success"] is True
@pytest.mark.asyncio
async def test_delegates_to_pull_requests(self):
"""Should delegate list_pull_requests to PR handler."""
plugin = GiteaPlugin(config={"url": "https://g.com", "token": "tk"})
plugin.client.list_pull_requests = AsyncMock(return_value=[])
result = json.loads(await plugin.list_pull_requests(owner="u", repo="r"))
assert result["success"] is True
@pytest.mark.asyncio
async def test_delegates_to_users(self):
"""Should delegate get_user to users handler."""
plugin = GiteaPlugin(config={"url": "https://g.com", "token": "tk"})
plugin.client.get_user = AsyncMock(return_value={"login": "test"})
result = json.loads(await plugin.get_user(username="test"))
assert result["success"] is True
@pytest.mark.asyncio
async def test_delegates_to_webhooks(self):
"""Should delegate list_webhooks to webhooks handler."""
plugin = GiteaPlugin(config={"url": "https://g.com", "token": "tk"})
plugin.client.list_webhooks = AsyncMock(return_value=[])
result = json.loads(await plugin.list_webhooks(owner="u", repo="r"))
assert result["success"] is True
@pytest.mark.asyncio
async def test_delegates_update_webhook(self):
"""Should delegate update_webhook to webhooks handler."""
plugin = GiteaPlugin(config={"url": "https://g.com", "token": "tk"})
plugin.client.update_webhook = AsyncMock(return_value={"id": 1})
result = json.loads(
await plugin.update_webhook(owner="u", repo="r", webhook_id=1, active=False)
)
assert result["success"] is True
@pytest.mark.asyncio
async def test_delegates_delete_label(self):
"""Should delegate delete_label to issues handler."""
plugin = GiteaPlugin(config={"url": "https://g.com", "token": "tk"})
plugin.client.delete_label = AsyncMock(return_value=None)
result = json.loads(await plugin.delete_label(owner="u", repo="r", label_id=1))
assert result["success"] is True