Skip to content

Commit 3038f32

Browse files
committed
Merge remote-tracking branch 'upstream/main' into RAG
2 parents 9b94aed + 00437c0 commit 3038f32

31 files changed

+2773
-240
lines changed

.github/workflows/run-ci-cd.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ jobs:
444444
445445
# Frontend
446446
touch .env.frontend
447+
echo "NEXT_SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" >> .env.frontend
447448
echo "NEXT_SERVER_CSRF_URL=${{ secrets.NEXT_SERVER_CSRF_URL }}" >> .env.frontend
448449
echo "NEXT_SERVER_GITHUB_CLIENT_ID=${{ secrets.GITHUB_CLIENT_ID }}" >> .env.frontend
449450
echo "NEXT_SERVER_GITHUB_CLIENT_SECRET=${{ secrets.GITHUB_CLIENT_SECRET }}" >> .env.frontend
@@ -675,6 +676,7 @@ jobs:
675676
676677
# Frontend
677678
touch .env.frontend
679+
echo "NEXT_SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" >> .env.frontend
678680
echo "NEXT_SERVER_CSRF_URL=${{ secrets.NEXT_SERVER_CSRF_URL }}" >> .env.frontend
679681
echo "NEXT_SERVER_GITHUB_CLIENT_ID=${{ secrets.GITHUB_CLIENT_ID }}" >> .env.frontend
680682
echo "NEXT_SERVER_GITHUB_CLIENT_SECRET=${{ secrets.GITHUB_CLIENT_SECRET }}" >> .env.frontend

backend/apps/owasp/models/common.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,13 @@ def get_metadata(self):
179179
get_repository_file_content(self.index_md_url),
180180
re.DOTALL,
181181
)
182-
return yaml.safe_load(yaml_content.group(1)) or {} if yaml_content else {}
182+
return yaml.safe_load(content) if (content := yaml_content.group(1)) else {}
183183
except (AttributeError, yaml.scanner.ScannerError):
184184
logger.exception(
185185
"Unable to parse entity metadata",
186186
extra={"repository": getattr(self.owasp_repository, "name", None)},
187187
)
188+
return {}
188189

189190
def get_related_url(self, url, exclude_domains=(), include_domains=()) -> str | None:
190191
"""Get OWASP entity related URL."""

backend/poetry.lock

Lines changed: 96 additions & 96 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/tests/apps/github/admin/__init__.py

Whitespace-only changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from unittest.mock import MagicMock
2+
3+
import pytest
4+
from django.contrib.admin.sites import AdminSite
5+
6+
from apps.github.admin.issue import IssueAdmin
7+
from apps.github.models.issue import Issue
8+
9+
10+
@pytest.fixture
11+
def issue_admin_instance():
12+
return IssueAdmin(model=Issue, admin_site=AdminSite())
13+
14+
15+
class TestIssueAdmin:
16+
"""Test suite for the IssueAdmin class."""
17+
18+
def test_custom_field_github_url(self, issue_admin_instance):
19+
"""Test that custom_field_github_url generates the correct HTML link."""
20+
mock_issue = MagicMock()
21+
mock_issue.url = "https://github.com/mock-org/mock-repo/issues/1"
22+
23+
result = issue_admin_instance.custom_field_github_url(mock_issue)
24+
25+
expected_html = (
26+
"<a href='https://github.com/mock-org/mock-repo/issues/1' target='_blank'>↗️</a>"
27+
)
28+
29+
assert result == expected_html
30+
31+
def test_list_display_contains_custom_field(self, issue_admin_instance):
32+
"""Test that the list_display includes custom fields."""
33+
assert "custom_field_github_url" in issue_admin_instance.list_display
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from unittest.mock import MagicMock
2+
3+
import pytest
4+
from django.contrib.admin.sites import AdminSite
5+
6+
from apps.github.admin.pull_request import PullRequestAdmin
7+
from apps.github.models.pull_request import PullRequest
8+
9+
10+
@pytest.fixture
11+
def pull_request_admin_instance():
12+
return PullRequestAdmin(model=PullRequest, admin_site=AdminSite())
13+
14+
15+
class TestPullRequestAdmin:
16+
"""Test suite for the PullRequestAdmin class."""
17+
18+
def test_custom_field_github_url(self, pull_request_admin_instance):
19+
"""Test that custom_field_github_url generates the correct HTML link."""
20+
mock_pull_request = MagicMock()
21+
mock_pull_request.url = "https://github.com/mock-org/mock-repo/pull/42"
22+
23+
result = pull_request_admin_instance.custom_field_github_url(mock_pull_request)
24+
25+
expected_html = (
26+
"<a href='https://github.com/mock-org/mock-repo/pull/42' target='_blank'>↗️</a>"
27+
)
28+
29+
assert result == expected_html
30+
31+
def test_list_display_contains_custom_field(self, pull_request_admin_instance):
32+
"""Test that the list_display includes custom fields."""
33+
assert "custom_field_github_url" in pull_request_admin_instance.list_display
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from unittest.mock import MagicMock
2+
3+
import pytest
4+
from django.contrib.admin.sites import AdminSite
5+
6+
from apps.github.admin.repository import RepositoryAdmin
7+
from apps.github.models.repository import Repository
8+
9+
10+
@pytest.fixture
11+
def repository_admin_instance():
12+
return RepositoryAdmin(model=Repository, admin_site=AdminSite())
13+
14+
15+
class TestRepositoryAdmin:
16+
"""Test suite for the RepositoryAdmin class."""
17+
18+
def test_custom_field_title(self, repository_admin_instance):
19+
mock_repository = MagicMock()
20+
mock_repository.owner.login = "mock-owner"
21+
mock_repository.name = "mock-repo"
22+
23+
result = repository_admin_instance.custom_field_title(mock_repository)
24+
25+
assert result == "mock-owner/mock-repo"
26+
27+
def test_custom_field_github_url(self, repository_admin_instance):
28+
"""Test that custom_field_github_url generates the correct HTML link."""
29+
mock_repository = MagicMock()
30+
mock_repository.owner.login = "mock-owner"
31+
mock_repository.name = "mock-repo"
32+
33+
result = repository_admin_instance.custom_field_github_url(mock_repository)
34+
expected_html = "<a href='https://github.com/mock-owner/mock-repo' target='_blank'>↗️</a>"
35+
36+
assert result == expected_html
37+
38+
def test_list_display_contains_custom_fields(self, repository_admin_instance):
39+
"""Test that the list_display includes custom fields."""
40+
admin_list_display = repository_admin_instance.list_display
41+
42+
assert "custom_field_title" in admin_list_display
43+
assert "custom_field_github_url" in admin_list_display

backend/tests/apps/github/api/internal/queries/issue_test.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,116 @@ def test_recent_issues_limit(self, mock_select_related, mock_issue):
7575
mock_select_related.assert_called_once()
7676
mock_queryset.order_by.assert_called_once_with("-created_at")
7777
assert result == [mock_issue]
78+
79+
@patch("apps.github.api.internal.queries.issue.Subquery")
80+
@patch("apps.github.models.issue.Issue.objects.select_related")
81+
def test_recent_issues_distinct(self, mock_select_related, mock_subquery, mock_issue):
82+
"""Test distinct filtering with Subquery for issues."""
83+
mock_queryset = MagicMock()
84+
mock_queryset.order_by.return_value = mock_queryset
85+
mock_queryset.filter.return_value = mock_queryset
86+
mock_queryset.__getitem__.return_value = [mock_issue]
87+
mock_select_related.return_value = mock_queryset
88+
89+
mock_subquery_instance = MagicMock()
90+
mock_subquery.return_value = mock_subquery_instance
91+
92+
result = IssueQuery().recent_issues(distinct=True)
93+
94+
assert result == [mock_issue]
95+
mock_subquery.assert_called_once()
96+
mock_queryset.filter.assert_called()
97+
98+
@patch("apps.github.models.issue.Issue.objects.select_related")
99+
def test_recent_issues_combined_filters(self, mock_select_related, mock_issue):
100+
mock_queryset = MagicMock()
101+
mock_queryset.order_by.return_value.filter.return_value.filter.return_value = [mock_issue]
102+
mock_select_related.return_value = mock_queryset
103+
104+
result = IssueQuery().recent_issues(login="alice", organization="owasp")
105+
106+
mock_select_related.assert_called_once()
107+
mock_queryset.order_by.assert_called_once()
108+
assert result == [mock_issue]
109+
110+
@patch("apps.github.api.internal.queries.issue.OuterRef")
111+
@patch("apps.github.api.internal.queries.issue.Subquery")
112+
@patch("apps.github.models.issue.Issue.objects.select_related")
113+
def test_recent_issues_distinct_with_organization(
114+
self, mock_select_related, mock_subquery, mock_outer_ref, mock_issue
115+
):
116+
"""Test distinct filtering with organization filter."""
117+
mock_queryset = MagicMock()
118+
mock_queryset.order_by.return_value = mock_queryset
119+
mock_queryset.filter.return_value = mock_queryset
120+
mock_queryset.__getitem__.return_value = [mock_issue]
121+
mock_select_related.return_value = mock_queryset
122+
123+
mock_subquery_instance = Mock()
124+
mock_subquery.return_value = mock_subquery_instance
125+
mock_outer_ref_instance = Mock()
126+
mock_outer_ref.return_value = mock_outer_ref_instance
127+
128+
result = IssueQuery().recent_issues(distinct=True, organization="owasp")
129+
130+
assert result == [mock_issue]
131+
mock_subquery.assert_called_once()
132+
mock_outer_ref.assert_called_once_with("author_id")
133+
134+
@patch("apps.github.api.internal.queries.issue.OuterRef")
135+
@patch("apps.github.api.internal.queries.issue.Subquery")
136+
@patch("apps.github.models.issue.Issue.objects.select_related")
137+
def test_recent_issues_distinct_with_all_filters(
138+
self, mock_select_related, mock_subquery, mock_outer_ref, mock_issue
139+
):
140+
"""Test distinct filtering with all filters."""
141+
mock_queryset = MagicMock()
142+
mock_queryset.order_by.return_value = mock_queryset
143+
mock_queryset.filter.return_value = mock_queryset
144+
mock_queryset.__getitem__.return_value = [mock_issue]
145+
mock_select_related.return_value = mock_queryset
146+
147+
mock_subquery_instance = Mock()
148+
mock_subquery.return_value = mock_subquery_instance
149+
mock_outer_ref_instance = Mock()
150+
mock_outer_ref.return_value = mock_outer_ref_instance
151+
152+
result = IssueQuery().recent_issues(
153+
distinct=True, login="alice", organization="owasp", limit=3
154+
)
155+
156+
assert result == [mock_issue]
157+
mock_subquery.assert_called_once()
158+
mock_outer_ref.assert_called_once_with("author_id")
159+
# Verify both login and organization filters were applied
160+
assert mock_queryset.filter.call_count >= 3 # login, organization, distinct
161+
162+
@patch("apps.github.models.issue.Issue.objects.select_related")
163+
def test_recent_issues_organization_only(self, mock_select_related, mock_issue):
164+
"""Test filtering issues by organization only."""
165+
mock_queryset = MagicMock()
166+
mock_queryset.order_by.return_value = mock_queryset
167+
mock_queryset.filter.return_value = mock_queryset
168+
mock_queryset.__getitem__.return_value = [mock_issue]
169+
mock_select_related.return_value = mock_queryset
170+
171+
result = IssueQuery().recent_issues(organization="owasp")
172+
173+
assert result == [mock_issue]
174+
assert len(mock_queryset.filter.call_args_list) >= 1
175+
176+
@patch("apps.github.models.issue.Issue.objects.select_related")
177+
def test_recent_issues_multiple_filters(self, mock_select_related, mock_issue):
178+
"""Test issues with multiple filters applied."""
179+
mock_queryset = MagicMock()
180+
mock_queryset.order_by.return_value = mock_queryset
181+
mock_queryset.filter.return_value = mock_queryset
182+
mock_queryset.__getitem__.return_value = [mock_issue]
183+
mock_select_related.return_value = mock_queryset
184+
185+
result = IssueQuery().recent_issues(organization="owasp", limit=10)
186+
187+
assert result == [mock_issue]
188+
# Verify organization filter was applied
189+
assert len(mock_queryset.filter.call_args_list) >= 1
190+
mock_queryset.__getitem__.assert_called_with(slice(None, 10))

backend/tests/apps/github/api/internal/queries/milestone_test.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from unittest.mock import MagicMock, Mock, patch
44

55
import pytest
6+
from django.core.exceptions import ValidationError
67

78
from apps.github.api.internal.queries.milestone import MilestoneQuery
89
from apps.github.models.milestone import Milestone
@@ -94,3 +95,25 @@ def test_recent_milestones_distinct(self):
9495

9596
assert isinstance(result, list)
9697
assert filtered_queryset.__getitem__.called
98+
99+
def test_recent_milestones_invalid_state(self):
100+
"""Test ValidationError for invalid state parameter."""
101+
with pytest.raises(ValidationError) as exc_info:
102+
MilestoneQuery().recent_milestones(state="invalid")
103+
104+
assert "Invalid state: invalid" in str(exc_info.value)
105+
assert "Valid states are 'open', 'closed', or 'all'" in str(exc_info.value)
106+
107+
def test_recent_milestones_with_all_parameters(self, get_queryset):
108+
"""Test recent milestones with all parameters provided."""
109+
with patch.object(Milestone, "closed_milestones", new_callable=Mock) as mock_manager:
110+
mock_manager.all.return_value = get_queryset
111+
112+
result = MilestoneQuery().recent_milestones(
113+
distinct=False, limit=10, login="testuser", organization="owasp", state="closed"
114+
)
115+
116+
assert isinstance(result, list)
117+
get_queryset.filter.assert_any_call(author__login="testuser")
118+
get_queryset.filter.assert_any_call(repository__organization__login="owasp")
119+
get_queryset.__getitem__.assert_called_with(slice(None, 10))
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Test cases for OrganizationQuery."""
2+
3+
from unittest.mock import Mock, patch
4+
5+
import pytest
6+
7+
from apps.github.api.internal.queries.organization import OrganizationQuery
8+
from apps.github.models.organization import Organization
9+
10+
11+
class TestOrganizationQuery:
12+
"""Test cases for OrganizationQuery class."""
13+
14+
@pytest.fixture
15+
def mock_organization(self):
16+
"""Mock Organization instance."""
17+
org = Mock(spec=Organization)
18+
org.id = 1
19+
org.login = "owasp"
20+
return org
21+
22+
@patch("apps.github.models.organization.Organization.objects.get")
23+
def test_organization_found(self, mock_get, mock_organization):
24+
"""Test fetching organization when it exists."""
25+
mock_get.return_value = mock_organization
26+
27+
result = OrganizationQuery().organization(login="owasp")
28+
29+
assert result == mock_organization
30+
mock_get.assert_called_once_with(is_owasp_related_organization=True, login="owasp")
31+
32+
@patch("apps.github.models.organization.Organization.objects.get")
33+
def test_organization_not_found(self, mock_get):
34+
"""Test fetching organization when it doesn't exist."""
35+
mock_get.side_effect = Organization.DoesNotExist()
36+
37+
result = OrganizationQuery().organization(login="nonexistent")
38+
39+
assert result is None
40+
mock_get.assert_called_once_with(is_owasp_related_organization=True, login="nonexistent")
41+
42+
@patch("apps.github.models.organization.Organization.objects.get")
43+
def test_organization_with_different_login(self, mock_get, mock_organization):
44+
"""Test fetching organization with different login."""
45+
mock_get.return_value = mock_organization
46+
47+
result = OrganizationQuery().organization(login="test-org")
48+
49+
assert result == mock_organization
50+
mock_get.assert_called_once_with(is_owasp_related_organization=True, login="test-org")

0 commit comments

Comments
 (0)