Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
135 changes: 130 additions & 5 deletions backend/app/services/agents/tests/test_code_audit_agent.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import pytest
import pytest_asyncio
import respx
from httpx import Response
from httpx import Response, Request, HTTPStatusError, RequestError
from backend.app.services.agents.code_audit_agent import CodeAuditAgent, CodeMetrics, AuditSummary, CodeAuditResult


@pytest_asyncio.fixture
async def code_audit_agent():
async with CodeAuditAgent() as agent:
Expand All @@ -22,7 +23,6 @@ async def test_fetch_github_repo_metrics(code_audit_agent):
respx.get(f"https://api.github.com/repos/{owner}/{repo}/contributors?per_page=1").mock(return_value=Response(200, headers={'link': '<https://api.github.com/repositories/1296269/contributors?per_page=1&page=2>; rel="next", <https://api.github.com/repositories/1296269/contributors?per_page=1&page=5>; rel="last"'}, json=[]))
# Mock latest release
respx.get(f"https://api.github.com/repos/{owner}/{repo}/releases/latest").mock(return_value=Response(200, json={'tag_name': 'v1.0.0'}))
# Mock issues count using GitHub Search API
respx.get(f"https://api.github.com/search/issues?q=repo%3A{owner}%2F{repo}%2Btype%3Aissue&per_page=1").mock(return_value=Response(200, json={'total_count': 20}))
# Mock pull requests count
respx.get(f"https://api.github.com/repos/{owner}/{repo}/pulls?state=all&per_page=1").mock(return_value=Response(200, headers={'link': '<https://api.github.com/repositories/1296269/pulls?state=all&per_page=1&page=2>; rel="next", <https://api.github.com/repositories/1296269/pulls?state=all&per_page=1&page=15>; rel="last"'}, json=[]))
Expand Down Expand Up @@ -102,11 +102,11 @@ async def test_audit_codebase(code_audit_agent):

with respx.mock:
# Mock GitHub API calls for fetch_repo_metrics
respx.get(f"https://api.github.com/repos/{owner}/{repo}/commits?per_page=1").mock(return_value=Response(200, headers={'link': '<https://api.github.com/repositories/1296269/commits?per_page=1&page=2>; rel="next", <https://api.github.com/repositories/1296269/commits?per_page=1&page=10>; rel="last"'}))
respx.get(f"https://api.github.com/repos/{owner}/{repo}/contributors?per_page=1").mock(return_value=Response(200, headers={'link': '<https://api.github.com/repositories/1296269/contributors?per_page=1&page=2>; rel="next", <https://api.github.com/repositories/1296269/contributors?per_page=1&page=5>; rel="last"'}))
respx.get(f"https://api.github.com/repos/{owner}/{repo}/commits?per_page=1").mock(return_value=Response(200, headers={'link': '<https://api.github.com/repositories/1296269/commits?per_page=1&page=2>; rel="next", <https://api.github.com/repositories/1296269/commits?per_page=1&page=10>; rel="last"'}, json=[]))
respx.get(f"https://api.github.com/repos/{owner}/{repo}/contributors?per_page=1").mock(return_value=Response(200, headers={'link': '<https://api.github.com/repositories/1296269/contributors?per_page=1&page=2>; rel="next", <https://api.github.com/repositories/1296269/contributors?per_page=1&page=5>; rel="last"'}, json=[]))
respx.get(f"https://api.github.com/repos/{owner}/{repo}/releases/latest").mock(return_value=Response(200, json={'tag_name': 'v1.0.0'}))
respx.get(f"https://api.github.com/search/issues?q=repo%3A{owner}%2F{repo}%2Btype%3Aissue&per_page=1").mock(return_value=Response(200, json={'total_count': 20}))
respx.get(f"https://api.github.com/repos/{owner}/{repo}/pulls?state=all&per_page=1").mock(return_value=Response(200, headers={'link': '<https://api.github.com/repositories/1296269/pulls?state=all&per_page=1&page=2>; rel="next", <https://api.github.com/repositories/1296269/pulls?state=all&per_page=1&page=15>; rel="last"'}))
respx.get(f"https://api.github.com/repos/{owner}/{repo}/pulls?state=all&per_page=1").mock(return_value=Response(200, headers={'link': '<https://api.github.com/repositories/1296269/pulls?state=all&per_page=1&page=2>; rel="next", <https://api.github.com/repositories/1296269/pulls?state=all&per_page=1&page=15>; rel="last"'}, json=[]))

result = await code_audit_agent.audit_codebase(repo_url, project_name)

Expand All @@ -115,3 +115,128 @@ async def test_audit_codebase(code_audit_agent):
assert len(result.audit_summaries) == 2
assert project_name in result.audit_summaries[0].report_title

@pytest.mark.asyncio
async def test_fetch_github_repo_metrics_http_error(code_audit_agent):
repo_url = "https://github.com/nonexistent/repo"
owner = "nonexistent"
repo = "repo"

with respx.mock:
# Mock all GitHub API calls to return 404 Not Found
respx.get(f"https://api.github.com/repos/{owner}/{repo}/commits?per_page=1").mock(return_value=Response(404))
respx.get(f"https://api.github.com/repos/{owner}/{repo}/contributors?per_page=1").mock(return_value=Response(404))
respx.get(f"https://api.github.com/repos/{owner}/{repo}/releases/latest").mock(return_value=Response(404))
respx.get(f"https://api.github.com/search/issues?q=repo%3A{owner}%2F{repo}%2Btype%3Aissue&per_page=1").mock(return_value=Response(404))
respx.get(f"https://api.github.com/repos/{owner}/{repo}/pulls?state=all&per_page=1").mock(return_value=Response(404))

metrics = await code_audit_agent.fetch_repo_metrics(repo_url)

assert metrics.repo_url == repo_url
assert metrics.commits_count == 0
assert metrics.contributors_count == 0
assert metrics.latest_release == "N/A"
assert metrics.issues_count == 0
assert metrics.pull_requests_count == 0

@pytest.mark.asyncio
async def test_fetch_github_repo_metrics_network_error(code_audit_agent):
repo_url = "https://github.com/owner/repo"
owner = "owner"
repo = "repo"

with respx.mock:
# Mock all GitHub API calls to raise a RequestError
respx.get(f"https://api.github.com/repos/{owner}/{repo}/commits?per_page=1").mock(side_effect=RequestError("Network error", request=Request("GET", f"https://api.github.com/repos/{owner}/{repo}/commits?per_page=1")))
respx.get(f"https://api.github.com/repos/{owner}/{repo}/contributors?per_page=1").mock(side_effect=RequestError("Network error", request=Request("GET", f"https://api.github.com/repos/{owner}/{repo}/contributors?per_page=1")))
respx.get(f"https://api.github.com/repos/{owner}/{repo}/releases/latest").mock(side_effect=RequestError("Network error", request=Request("GET", f"https://api.github.com/repos/{owner}/{repo}/releases/latest")))
respx.get(f"https://api.github.com/search/issues?q=repo%3A{owner}%2F{repo}%2Btype%3Aissue&per_page=1").mock(side_effect=RequestError("Network error", request=Request("GET", f"https://api.github.com/search/issues?q=repo%3A{owner}%2F{repo}%2Btype%3Aissue&per_page=1")))
respx.get(f"https://api.github.com/repos/{owner}/{repo}/pulls?state=all&per_page=1").mock(side_effect=RequestError("Network error", request=Request("GET", f"https://api.github.com/repos/{owner}/{repo}/pulls?state=all&per_page=1")))

metrics = await code_audit_agent.fetch_repo_metrics(repo_url)

assert metrics.repo_url == repo_url
assert metrics.commits_count == 0
assert metrics.contributors_count == 0
assert metrics.latest_release == "N/A"
assert metrics.issues_count == 0
assert metrics.pull_requests_count == 0

@pytest.mark.asyncio
async def test_fetch_gitlab_repo_metrics_http_error(code_audit_agent):
repo_url = "https://gitlab.com/nonexistent/repo"
project_id = "nonexistent%2Frepo"

with respx.mock:
# Mock all GitLab API calls to return 404 Not Found
respx.get(f"https://gitlab.com/api/v4/projects/{project_id}/repository/commits?per_page=1").mock(return_value=Response(404))
respx.get(f"https://gitlab.com/api/v4/projects/{project_id}/repository/contributors?per_page=1").mock(return_value=Response(404))
respx.get(f"https://gitlab.com/api/v4/projects/{project_id}/repository/tags?per_page=1").mock(return_value=Response(404))
respx.get(f"https://gitlab.com/api/v4/projects/{project_id}/issues?scope=all&per_page=1").mock(return_value=Response(404))
respx.get(f"https://gitlab.com/api/v4/projects/{project_id}/merge_requests?scope=all&per_page=1").mock(return_value=Response(404))

metrics = await code_audit_agent.fetch_repo_metrics(repo_url)

assert metrics.repo_url == repo_url
assert metrics.commits_count == 0
assert metrics.contributors_count == 0
assert metrics.latest_release == "N/A"
assert metrics.issues_count == 0
assert metrics.pull_requests_count == 0

@pytest.mark.asyncio
async def test_fetch_gitlab_repo_metrics_network_error(code_audit_agent):
repo_url = "https://gitlab.com/group/project"
project_id = "group%2Fproject"

with respx.mock:
# Mock all GitLab API calls to raise a RequestError
respx.get(f"https://gitlab.com/api/v4/projects/{project_id}/repository/commits?per_page=1").mock(side_effect=RequestError("Network error", request=Request("GET", f"https://gitlab.com/api/v4/projects/{project_id}/repository/commits?per_page=1")))
respx.get(f"https://gitlab.com/api/v4/projects/{project_id}/repository/contributors?per_page=1").mock(side_effect=RequestError("Network error", request=Request("GET", f"https://gitlab.com/api/v4/projects/{project_id}/repository/contributors?per_page=1")))
respx.get(f"https://gitlab.com/api/v4/projects/{project_id}/repository/tags?per_page=1").mock(side_effect=RequestError("Network error", request=Request("GET", f"https://gitlab.com/api/v4/projects/{project_id}/repository/tags?per_page=1")))
respx.get(f"https://gitlab.com/api/v4/projects/{project_id}/issues?scope=all&per_page=1").mock(side_effect=RequestError("Network error", request=Request("GET", f"https://gitlab.com/api/v4/projects/{project_id}/issues?scope=all&per_page=1")))
respx.get(f"https://gitlab.com/api/v4/projects/{project_id}/merge_requests?scope=all&per_page=1").mock(side_effect=RequestError("Network error", request=Request("GET", f"https://gitlab.com/api/v4/projects/{project_id}/merge_requests?scope=all&per_page=1")))

metrics = await code_audit_agent.fetch_repo_metrics(repo_url)

assert metrics.repo_url == repo_url
assert metrics.commits_count == 0
assert metrics.contributors_count == 0
assert metrics.latest_release == "N/A"
assert metrics.issues_count == 0
assert metrics.pull_requests_count == 0

@pytest.mark.asyncio
async def test_fetch_repo_metrics_unsupported_url(code_audit_agent):
repo_url = "https://bitbucket.org/some/repo"
metrics = await code_audit_agent.fetch_repo_metrics(repo_url)

assert metrics.repo_url == repo_url
assert metrics.commits_count == 0
assert metrics.contributors_count == 0
assert metrics.latest_release == "N/A"
assert metrics.issues_count == 0
assert metrics.pull_requests_count == 0

@pytest.mark.asyncio
async def test_fetch_repo_metrics_invalid_github_url_format(code_audit_agent):
repo_url = "https://github.com/owner_only"
metrics = await code_audit_agent.fetch_repo_metrics(repo_url)

assert metrics.repo_url == repo_url
assert metrics.commits_count == 0
assert metrics.contributors_count == 0
assert metrics.latest_release == "N/A"
assert metrics.issues_count == 0
assert metrics.pull_requests_count == 0

@pytest.mark.asyncio
async def test_fetch_repo_metrics_invalid_gitlab_url_format(code_audit_agent):
repo_url = "https://gitlab.com/group_only"
metrics = await code_audit_agent.fetch_repo_metrics(repo_url)

assert metrics.repo_url == repo_url
assert metrics.commits_count == 0
assert metrics.contributors_count == 0
assert metrics.latest_release == "N/A"
assert metrics.issues_count == 0
assert metrics.pull_requests_count == 0