Skip to content

Conversation

@trucodd
Copy link
Contributor

@trucodd trucodd commented Aug 11, 2025

…updated

Proposed change

Resolves #1797

Improved test coverage

apps/github/api/internal/nodes/issue.py                                           15      0      0      0 100.00%
apps/github/api/internal/nodes/milestone.py                                       21      0      2      0 100.00%
apps/github/api/internal/nodes/organization.py                                    28      0      0      0 100.00%
apps/github/api/internal/nodes/pull_request.py                                    18      0      0      0 100.00%
apps/github/api/internal/nodes/release.py                                         22      0      0      0 100.00%
apps/github/api/internal/nodes/repository.py                                      46      0      0      0 100.00%
apps/github/api/internal/nodes/repository_contributor.py                           9      0      0      0 100.00%
apps/github/api/internal/nodes/user.py                                            20      0      0      0 100.00%

Checklist

  • I've read and followed the contributing guidelines.
  • I've run make check-test locally; all checks and tests passed.

@trucodd trucodd requested a review from arkid15r as a code owner August 11, 2025 02:46
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 11, 2025

Summary by CodeRabbit

  • New Features
    • Repository: added fields for commits_count, description, latest_release, organization, owner_key, project, releases, top_contributors, updated_at, and url.
  • Changes
    • Repository: renamed issues_count to issues.
    • Repository: removed is_active, level, type, summary, recent_issues, recent_pull_requests, recent_releases, repositories, and repositories_count.
  • Tests
    • Expanded unit test coverage for Issue, Milestone, Organization (stats, url), Pull Request, Release, Repository (fields and resolvers), and User nodes to validate field resolution and behaviors.

Walkthrough

Adds comprehensive unit tests for multiple GitHub GraphQL node types (Issue, Milestone, Organization, PullRequest, Release, Repository, User) exercising field resolvers, Strawberry definitions, and edge cases; no production code changes.

Changes

Cohort / File(s) Summary of Changes
IssueNode tests
backend/tests/apps/github/api/internal/nodes/issue_test.py
Added tests for IssueNode resolvers: author, organization_name (with/without organization/repository), repository_name (with/without repository).
MilestoneNode tests
backend/tests/apps/github/api/internal/nodes/milestone_test.py
Added tests for MilestoneNode resolvers: author, organization_name, repository_name, and progress calculation (with/without issues).
OrganizationNode tests
backend/tests/apps/github/api/internal/nodes/organization_test.py
Added tests for OrganizationNode.stats aggregation (handles None -> 0) and url; includes OrganizationStatsNode instantiation checks; patches model querysets.
PullRequestNode tests
backend/tests/apps/github/api/internal/nodes/pull_request_test.py
Added tests for PullRequestNode Strawberry fields and resolvers: author, organization_name, repository_name, url (empty and present), and field set verification.
ReleaseNode tests
backend/tests/apps/github/api/internal/nodes/release_test.py
Added tests for ReleaseNode resolvers: author, organization_name, project_name (prefix stripping and None), repository_name, and url.
RepositoryNode tests
backend/tests/apps/github/api/internal/nodes/repository_test.py
Reworked tests to target RepositoryNode: updated Strawberry field expectations and added resolver tests for issues, languages, latest_release, organization, owner_key, project, recent_milestones, releases, top_contributors, topics, url.
UserNode tests
backend/tests/apps/github/api/internal/nodes/user_test.py
Added tests for UserNode resolvers: created_at, issues_count, releases_count, updated_at, and url.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
Add/expand tests for Issue, Milestone, Organization, Pull Request, Release, User nodes [#1797]
Expand RepositoryNode tests to improve coverage [#1797]
Reach ≥80% coverage for listed modules (coverage targets) [#1797] No coverage report or CI measurement included in the PR to confirm post-merge percentages.

Possibly related PRs

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@trucodd trucodd marked this pull request as draft August 11, 2025 02:50
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (11)
backend/tests/apps/github/api/internal/nodes/user_test.py (2)

11-13: Nit: Clarify the test name/docstring

Docstring says “inherits from BaseNode,” but the assertion checks for a Strawberry definition. Consider updating the docstring to reflect what’s actually verified.

-    def test_user_node_inheritance(self):
-        """Test if UserNode inherits from BaseNode."""
+    def test_user_node_is_strawberry_type(self):
+        """Test if UserNode is a Strawberry type (has __strawberry_definition__)."""

15-38: Avoid brittle schema assertions; use subset instead of equality

Schema may evolve. Asserting exact equality makes tests fragile. Prefer subset to ensure required fields exist without blocking additive changes.

-        assert field_names == expected_field_names
+        assert expected_field_names.issubset(field_names)
backend/tests/apps/github/api/internal/nodes/milestone_test.py (2)

14-29: Relax strict field equality to reduce flakiness

Use subset to guard against future additive fields in the Strawberry type.

-        assert field_names == expected_field_names
+        assert expected_field_names.issubset(field_names)

70-87: Add a rounding edge-case for progress

Consider adding a case that exercises rounding (e.g., 1 closed, 2 open → 33.33) to assert the two-decimal behavior.

+    def test_progress_rounding(self):
+        """Test progress rounding to two decimals."""
+        m = Mock()
+        m.closed_issues_count = 1
+        m.open_issues_count = 2
+        assert MilestoneNode.progress(m) == 33.33
backend/tests/apps/github/api/internal/nodes/release_test.py (1)

15-28: Prefer subset over equality for field lists

Use subset to future-proof tests against additive fields in the GraphQL type.

-        assert field_names == expected_field_names
+        assert expected_field_names.issubset(field_names)
backend/tests/apps/github/api/internal/nodes/issue_test.py (1)

14-25: Make schema checks resilient

Prefer subset checks to avoid brittleness when fields are added.

-        assert field_names == expected_field_names
+        assert expected_field_names.issubset(field_names)
backend/tests/apps/github/api/internal/nodes/pull_request_test.py (1)

14-24: Reduce flakiness in schema assertions

Use subset instead of equality to handle additive schema changes gracefully.

-        assert field_names == expected_field_names
+        assert expected_field_names.issubset(field_names)
backend/tests/apps/github/api/internal/nodes/organization_test.py (1)

15-32: Optional: relax equality on schema fields

If the OrganizationNode schema gains fields, this test will start failing. Consider subset checks.

-        assert field_names == expected_field_names
+        assert expected_field_names.issubset(field_names)
backend/tests/apps/github/api/internal/nodes/repository_test.py (3)

57-61: Make list type assertions version-robust

Strawberry/Python versions can differ in how list types are represented (list[str] vs typing.List[str]). Use typing.get_origin/get_args to avoid false negatives.

+from typing import get_origin, get_args, List
...
-        assert field.type == list[str]
+        assert get_origin(field.type) in {list, List}
+        assert get_args(field.type) in { (str,), (str,) }
...
-        assert field.type == list[str]
+        assert get_origin(field.type) in {list, List}
+        assert get_args(field.type) in { (str,), (str,) }

Also applies to: 92-96


102-114: Verify slicing is invoked for issues

You’re validating select_related/order_by; also assert that slicing occurred to ensure the limit behavior is exercised.

         RepositoryNode.issues(mock_repository)
         mock_issues.select_related.assert_called_with("author")
         mock_issues.select_related.return_value.order_by.assert_called_with("-created_at")
+        mock_issues.select_related.return_value.order_by.return_value.__getitem__.assert_called()

170-179: Likewise, assert slicing for releases

Add an assertion that slicing was performed after ordering by published_at.

         RepositoryNode.releases(mock_repository)
         mock_releases.order_by.assert_called_with("-published_at")
+        mock_releases.order_by.return_value.__getitem__.assert_called()
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e137c07 and f137e66.

📒 Files selected for processing (7)
  • backend/tests/apps/github/api/internal/nodes/issue_test.py (2 hunks)
  • backend/tests/apps/github/api/internal/nodes/milestone_test.py (2 hunks)
  • backend/tests/apps/github/api/internal/nodes/organization_test.py (3 hunks)
  • backend/tests/apps/github/api/internal/nodes/pull_request_test.py (1 hunks)
  • backend/tests/apps/github/api/internal/nodes/release_test.py (2 hunks)
  • backend/tests/apps/github/api/internal/nodes/repository_test.py (1 hunks)
  • backend/tests/apps/github/api/internal/nodes/user_test.py (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
backend/tests/apps/github/api/internal/nodes/pull_request_test.py (1)
backend/apps/github/api/internal/nodes/pull_request.py (1)
  • PullRequestNode (17-42)
backend/tests/apps/github/api/internal/nodes/user_test.py (3)
backend/apps/github/api/internal/nodes/user.py (6)
  • UserNode (27-53)
  • created_at (31-33)
  • issues_count (36-38)
  • releases_count (41-43)
  • updated_at (46-48)
  • url (51-53)
backend/apps/github/models/mixins/user.py (1)
  • idx_releases_count (172-174)
backend/tests/apps/github/api/internal/nodes/release_test.py (1)
  • test_url_field (123-129)
backend/tests/apps/github/api/internal/nodes/release_test.py (1)
backend/apps/github/api/internal/nodes/release.py (1)
  • ReleaseNode (20-54)
backend/tests/apps/github/api/internal/nodes/organization_test.py (3)
backend/tests/apps/github/api/internal/queries/organization_test.py (1)
  • mock_organization (15-20)
backend/apps/github/api/internal/nodes/organization.py (3)
  • OrganizationNode (39-76)
  • stats (43-71)
  • OrganizationStatsNode (13-20)
backend/tests/apps/github/api/internal/nodes/repository_test.py (1)
  • test_url_method (197-203)
backend/tests/apps/github/api/internal/nodes/issue_test.py (3)
backend/tests/apps/github/api/internal/nodes/milestone_test.py (6)
  • test_author_field (31-38)
  • test_organization_name_with_organization (40-50)
  • test_organization_name_without_organization (52-60)
  • test_organization_name_without_repository (62-68)
  • test_repository_name_with_repository (88-96)
  • test_repository_name_without_repository (98-104)
backend/tests/apps/github/api/internal/nodes/pull_request_test.py (6)
  • test_author_field (26-33)
  • test_organization_name_with_organization (35-45)
  • test_organization_name_without_organization (47-55)
  • test_organization_name_without_repository (57-63)
  • test_repository_name_with_repository (65-73)
  • test_repository_name_without_repository (75-81)
backend/apps/github/api/internal/nodes/issue.py (1)
  • IssueNode (19-39)
backend/tests/apps/github/api/internal/nodes/milestone_test.py (1)
backend/apps/github/api/internal/nodes/milestone.py (2)
  • MilestoneNode (22-50)
  • progress (40-45)
backend/tests/apps/github/api/internal/nodes/repository_test.py (5)
backend/apps/github/api/internal/nodes/issue.py (1)
  • IssueNode (19-39)
backend/apps/github/api/internal/nodes/organization.py (1)
  • OrganizationNode (39-76)
backend/apps/github/api/internal/nodes/release.py (1)
  • ReleaseNode (20-54)
backend/apps/github/api/internal/nodes/repository.py (2)
  • RepositoryNode (40-100)
  • owner_key (65-67)
backend/apps/github/api/internal/nodes/repository_contributor.py (1)
  • RepositoryContributorNode (7-15)
🔇 Additional comments (12)
backend/tests/apps/github/api/internal/nodes/user_test.py (1)

40-78: Resolver tests look good

Field resolver tests for created_at, issues_count, releases_count, updated_at, and url are straightforward and accurate.

backend/tests/apps/github/api/internal/nodes/milestone_test.py (1)

31-39: LGTM on resolver coverage

Author, organization_name (present/absent), and repository_name (present/absent) test cases are precise and valuable.

Also applies to: 88-105

backend/tests/apps/github/api/internal/nodes/release_test.py (1)

36-44: LGTM on resolver tests

Author, organization_name (present/absent), repository_name (present/absent), and url resolution tests are accurate.

Also applies to: 105-122, 123-129

backend/tests/apps/github/api/internal/nodes/issue_test.py (1)

27-83: LGTM on resolver coverage

Author and organization_name/repository_name tests cover both present and absent nested attributes effectively.

backend/tests/apps/github/api/internal/nodes/pull_request_test.py (1)

83-97: LGTM on url resolver behavior

Correctly asserts empty string when url is None and returns the given URL otherwise.

backend/tests/apps/github/api/internal/nodes/organization_test.py (3)

49-67: Patch targets and ordering look correct

Patching Repository.objects and RepositoryContributor.objects at module paths used by OrganizationNode, with parameter order matching patch stacking, is correct.


82-116: Solid coverage for stats edge cases

Good handling of both actual values and None aggregates (defaulting to 0). This meaningfully exercises the resolver logic.


126-138: Stats node fields assertion is fine

Exact field set on OrganizationStatsNode is appropriate here and improves schema safety.

backend/tests/apps/github/api/internal/nodes/repository_test.py (4)

17-45: Good use of subset for meta configuration

Using issubset here reduces brittleness while still enforcing key fields.


131-139: LGTM on resolver behavior tests

Direct-return resolvers (latest_release, organization, owner_key, project, recent_milestones ordering, top_contributors, topics, url) are covered well and read clearly.

Also applies to: 140-147, 148-156, 157-169, 180-188, 189-203


52-56: Schema type checks look good

Validations that issues/recent_milestones/releases/top_contributors are typed as lists of expected node types, and that url/owner_key/latest_release are str, are appropriate.

Also applies to: 62-76, 77-91, 97-101


1-204: Verify internal node modules coverage (≥ 80%)

The sandbox environment doesn’t include pytest, so please run this locally to confirm that all files under backend/apps/github/api/internal/nodes meet the 80% coverage threshold:

#!/bin/bash
set -euo pipefail

# Run tests for internal nodes with coverage
pytest -q \
  backend/tests/apps/github/api/internal/nodes \
  --cov=backend/apps/github/api/internal/nodes \
  --cov-report=term-missing

# Enforce per-file threshold of 80%
coverage xml -o coverage.xml
python - <<'PY'
import xml.etree.ElementTree as ET
tree = ET.parse('coverage.xml')
root = tree.getroot()
fail = False
print("Per-file coverage for backend/apps/github/api/internal/nodes:")
for pkg in root.findall(".//packages/package"):
    for cls in pkg.findall("classes/class"):
        filename = cls.attrib["filename"]
        if filename.startswith("backend/apps/github/api/internal/nodes/") and filename.endswith(".py"):
            lines = cls.find("lines")
            covered = sum(
                1 for l in lines 
                for li in l.findall("line") 
                if float(li.attrib.get("hits", "0")) > 0
            )
            total = sum(len(l.findall("line")) for l in lines)
            pct = (covered / total * 100) if total else 100.0
            print(f"  {filename}: {pct:.2f}%")
            if pct < 80.0:
                fail = True
if fail:
    raise SystemExit("Coverage threshold not met (>=80% required).")
print("All target files meet or exceed 80%.")
PY

Please verify and share the coverage report.

Comment on lines +75 to +86
def test_project_name_with_project(self):
"""Test project_name field when project exists."""
mock_release = Mock()
mock_repository = Mock()
mock_project = Mock()
mock_project.name = "OWASP Test Project"
mock_repository.project = mock_project
mock_release.repository = mock_repository

result = ReleaseNode.project_name(mock_release)
assert result == " Test Project" # OWASP prefix stripped

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Potential semantic pitfall with lstrip when stripping “OWASP” prefix

The production code uses str.lstrip(OWASP_ORGANIZATION_NAME). lstrip removes any leading characters contained in the argument, not the exact prefix. This can unexpectedly trim names that merely start with any of the letters in “OWASP”. Consider switching the implementation to removeprefix for exact semantics, e.g., name.removeprefix(OWASP_ORGANIZATION_NAME) or name.removeprefix(f"{OWASP_ORGANIZATION_NAME} ") if a trailing space is intended.

If you’d like, I can raise a follow-up issue and propose a patch to replace lstrip with removeprefix and update tests accordingly.


🏁 Script executed:

#!/bin/bash
# Locate the implementation of ReleaseNode.project_name
rg -n "def project_name" -R backend
# Find where OWASP_ORGANIZATION_NAME is defined or used
rg -n "OWASP_ORGANIZATION_NAME" -R backend

Length of output: 890


🏁 Script executed:

#!/bin/bash
# Locate the ReleaseNode class and its project_name method
rg -n "class ReleaseNode" backend
rg -n "def project_name" backend

# Inspect any usage of lstrip and the OWASP_ORGANIZATION_NAME constant
rg -n "lstrip" backend
rg -n "OWASP_ORGANIZATION_NAME" backend

Length of output: 2106


🏁 Script executed:

#!/bin/bash
# Display the ReleaseNode implementation to inspect project_name
sed -n '1,100p' backend/apps/github/api/internal/nodes/release.py

Length of output: 1450


Use removeprefix for exact “OWASP” removal instead of lstrip

The project_name resolver currently does:

self.repository.project.name.lstrip(OWASP_ORGANIZATION_NAME)

But str.lstrip(chars) strips any leading character found in chars, not the exact substring. That means names like "SOAP Project" or "PASSion Project" could get mangled.

Switch to str.removeprefix to remove only the literal "OWASP" (and its trailing space, if desired):

• backend/apps/github/api/internal/nodes/release.py

-    return (
-        self.repository.project.name.lstrip(OWASP_ORGANIZATION_NAME)
-        if self.repository and self.repository.project
-        else None
-    )
+    if not (self.repository and self.repository.project):
+        return None
+    return self.repository.project.name.removeprefix(f"{OWASP_ORGANIZATION_NAME} ")

• backend/tests/apps/github/api/internal/nodes/release_test.py

-        result = ReleaseNode.project_name(mock_release)
-        assert result == " Test Project"  # OWASP prefix stripped
+        result = ReleaseNode.project_name(mock_release)
+        assert result == "Test Project"  # "OWASP " prefix removed exactly

These changes ensure only the exact "OWASP" (plus space) is stripped, preventing unintended trimming of other names.

@trucodd trucodd force-pushed the feature/github-api-nodes-cov branch from f137e66 to 0b2432a Compare August 11, 2025 03:24
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
backend/tests/apps/github/api/internal/nodes/repository_test.py (4)

14-16: Rename test to reflect its intent (not inheritance).

The test asserts Strawberry type registration, not inheritance. Rename for clarity.

-def test_repository_node_inheritance(self):
+def test_repository_node_is_strawberry_type(self):
     assert hasattr(RepositoryNode, "__strawberry_definition__")

17-45: Strengthen meta-configuration assertion to catch regressions.

Use strict equality to surface unexpected additions/removals. Other node tests in this repo also use equality, so this aligns style and increases signal.

-assert expected_field_names.issubset(field_names)
+assert field_names == expected_field_names

115-122: Avoid order assumptions when asserting languages.

Dictionary key order is insertion-dependent. Asserting as a set makes the test robust.

 result = RepositoryNode.languages(mock_repository)
-assert result == ["Python", "JavaScript"]
+assert set(result) == {"Python", "JavaScript"}

17-45: Optional: add quick type assertions for scalar fields to close gaps.

For completeness and to bump coverage with minimal cost, consider adding type checks (like existing ones) for: key, name, description, license, commits_count, contributors_count, forks_count, open_issues_count, size, stars_count, subscribers_count, created_at, updated_at.

I can add a compact helper and parameterize assertions to keep the test concise.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f137e66 and 0b2432a.

📒 Files selected for processing (7)
  • backend/tests/apps/github/api/internal/nodes/issue_test.py (2 hunks)
  • backend/tests/apps/github/api/internal/nodes/milestone_test.py (2 hunks)
  • backend/tests/apps/github/api/internal/nodes/organization_test.py (3 hunks)
  • backend/tests/apps/github/api/internal/nodes/pull_request_test.py (1 hunks)
  • backend/tests/apps/github/api/internal/nodes/release_test.py (2 hunks)
  • backend/tests/apps/github/api/internal/nodes/repository_test.py (1 hunks)
  • backend/tests/apps/github/api/internal/nodes/user_test.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • backend/tests/apps/github/api/internal/nodes/milestone_test.py
  • backend/tests/apps/github/api/internal/nodes/issue_test.py
  • backend/tests/apps/github/api/internal/nodes/release_test.py
  • backend/tests/apps/github/api/internal/nodes/pull_request_test.py
  • backend/tests/apps/github/api/internal/nodes/organization_test.py
  • backend/tests/apps/github/api/internal/nodes/user_test.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/tests/apps/github/api/internal/nodes/repository_test.py (10)
backend/apps/github/api/internal/nodes/issue.py (1)
  • IssueNode (19-39)
backend/apps/github/api/internal/nodes/milestone.py (1)
  • MilestoneNode (22-50)
backend/apps/github/api/internal/nodes/organization.py (1)
  • OrganizationNode (39-76)
backend/apps/github/api/internal/nodes/release.py (1)
  • ReleaseNode (20-54)
backend/apps/github/api/internal/queries/repository.py (1)
  • repository (14-35)
backend/apps/github/api/internal/nodes/repository.py (2)
  • RepositoryNode (40-100)
  • owner_key (65-67)
backend/apps/github/api/internal/nodes/repository_contributor.py (1)
  • RepositoryContributorNode (7-15)
backend/tests/apps/github/api/internal/nodes/organization_test.py (3)
  • test_meta_configuration (15-32)
  • test_resolve_url (42-47)
  • test_url_method (117-123)
backend/tests/apps/github/api/internal/nodes/release_test.py (1)
  • test_meta_configuration (15-28)
backend/tests/apps/github/api/internal/queries/repository_test.py (1)
  • mock_repository (15-19)
🔇 Additional comments (1)
backend/tests/apps/github/api/internal/nodes/repository_test.py (1)

52-56: LGTM: Resolver and type introspection tests are on-point.

Good coverage of field types and resolver behaviors with lightweight mocks. Suggested tweaks above will harden the tests further.

Also applies to: 62-76, 77-81, 82-86, 87-91, 92-96, 97-101, 123-156, 180-188, 189-196, 197-204

Comment on lines 9 to +10
from apps.github.api.internal.nodes.repository import RepositoryNode
from apps.owasp.api.internal.nodes.project import ProjectNode
from apps.github.api.internal.nodes.repository_contributor import RepositoryContributorNode
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Import limits to assert slice sizes in resolver tests.

Asserting the slice size protects against silent regressions in pagination/limit behavior.

-from apps.github.api.internal.nodes.repository import RepositoryNode
+from apps.github.api.internal.nodes.repository import (
+    RepositoryNode,
+    RECENT_ISSUES_LIMIT,
+    RECENT_RELEASES_LIMIT,
+)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from apps.github.api.internal.nodes.repository import RepositoryNode
from apps.owasp.api.internal.nodes.project import ProjectNode
from apps.github.api.internal.nodes.repository_contributor import RepositoryContributorNode
from apps.github.api.internal.nodes.repository import (
RepositoryNode,
RECENT_ISSUES_LIMIT,
RECENT_RELEASES_LIMIT,
)
from apps.github.api.internal.nodes.repository_contributor import RepositoryContributorNode
🤖 Prompt for AI Agents
In backend/tests/apps/github/api/internal/nodes/repository_test.py around lines
9-10, add an import for the resolver's pagination limit constant (the
module/file where the resolver defines its default limit) and use that constant
in the test assertions that check slice sizes so the tests assert against the
actual configured limit (e.g. replace hardcoded numbers with the imported
LIMIT/PAGE_SIZE constant), ensuring future changes to the resolver limit are
reflected in tests.

Comment on lines +102 to +114
def test_issues_method(self):
"""Test issues method resolution."""
mock_repository = Mock()
mock_issues = Mock()
mock_issues.select_related.return_value.order_by.return_value.__getitem__ = Mock(
return_value=[]
)
mock_repository.issues = mock_issues

RepositoryNode.issues(mock_repository)
mock_issues.select_related.assert_called_with("author")
mock_issues.select_related.return_value.order_by.assert_called_with("-created_at")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Also assert the issues slice limit.

Verifies [:RECENT_ISSUES_LIMIT] is applied, not just the queryset chaining.

 RepositoryNode.issues(mock_repository)
 mock_issues.select_related.assert_called_with("author")
 mock_issues.select_related.return_value.order_by.assert_called_with("-created_at")
+mock_issues.select_related.return_value.order_by.return_value.__getitem__.assert_called_with(
+    slice(None, RECENT_ISSUES_LIMIT)
+)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_issues_method(self):
"""Test issues method resolution."""
mock_repository = Mock()
mock_issues = Mock()
mock_issues.select_related.return_value.order_by.return_value.__getitem__ = Mock(
return_value=[]
)
mock_repository.issues = mock_issues
RepositoryNode.issues(mock_repository)
mock_issues.select_related.assert_called_with("author")
mock_issues.select_related.return_value.order_by.assert_called_with("-created_at")
def test_issues_method(self):
"""Test issues method resolution."""
mock_repository = Mock()
mock_issues = Mock()
mock_issues.select_related.return_value.order_by.return_value.__getitem__ = Mock(
return_value=[]
)
mock_repository.issues = mock_issues
RepositoryNode.issues(mock_repository)
mock_issues.select_related.assert_called_with("author")
mock_issues.select_related.return_value.order_by.assert_called_with("-created_at")
+ mock_issues.select_related.return_value.order_by.return_value.__getitem__.assert_called_with(
+ slice(None, RECENT_ISSUES_LIMIT)
+ )
🤖 Prompt for AI Agents
In backend/tests/apps/github/api/internal/nodes/repository_test.py around lines
102 to 114, add an assertion that the issues queryset slicing is applied: after
calling RepositoryNode.issues(mock_repository), assert that
mock_issues.select_related.return_value.order_by.return_value.__getitem__ was
called with the slice corresponding to the RECENT_ISSUES_LIMIT (e.g. slice(None,
RepositoryNode.RECENT_ISSUES_LIMIT, None)) so the test verifies the
[:RECENT_ISSUES_LIMIT] limit is enforced.

Comment on lines +157 to +169
def test_recent_milestones_method(self):
"""Test recent_milestones method resolution."""
mock_repository = Mock()
mock_milestones = Mock()
mock_milestones.select_related.return_value.order_by.return_value.__getitem__ = Mock(
return_value=[]
)
mock_repository.recent_milestones = mock_milestones

RepositoryNode.recent_milestones(mock_repository, limit=3)
mock_milestones.select_related.assert_called_with("repository")
mock_milestones.select_related.return_value.order_by.assert_called_with("-created_at")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Assert recent_milestones respects the passed limit.

Confirms slicing is applied with the provided limit.

 RepositoryNode.recent_milestones(mock_repository, limit=3)
 mock_milestones.select_related.assert_called_with("repository")
 mock_milestones.select_related.return_value.order_by.assert_called_with("-created_at")
+mock_milestones.select_related.return_value.order_by.return_value.__getitem__.assert_called_with(
+    slice(None, 3)
+)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_recent_milestones_method(self):
"""Test recent_milestones method resolution."""
mock_repository = Mock()
mock_milestones = Mock()
mock_milestones.select_related.return_value.order_by.return_value.__getitem__ = Mock(
return_value=[]
)
mock_repository.recent_milestones = mock_milestones
RepositoryNode.recent_milestones(mock_repository, limit=3)
mock_milestones.select_related.assert_called_with("repository")
mock_milestones.select_related.return_value.order_by.assert_called_with("-created_at")
def test_recent_milestones_method(self):
"""Test recent_milestones method resolution."""
mock_repository = Mock()
mock_milestones = Mock()
mock_milestones.select_related.return_value.order_by.return_value.__getitem__ = Mock(
return_value=[]
)
mock_repository.recent_milestones = mock_milestones
RepositoryNode.recent_milestones(mock_repository, limit=3)
mock_milestones.select_related.assert_called_with("repository")
mock_milestones.select_related.return_value.order_by.assert_called_with("-created_at")
mock_milestones.select_related.return_value.order_by.return_value.__getitem__.assert_called_with(
slice(None, 3)
)
🤖 Prompt for AI Agents
In backend/tests/apps/github/api/internal/nodes/repository_test.py around lines
157 to 169, the test checks select_related and order_by but does not assert that
the returned queryset is sliced using the provided limit; add an assertion that
the mocked queryset's __getitem__ was called with a slice for the given limit
(e.g., slice(None, 3)) to confirm slicing was applied when calling
RepositoryNode.recent_milestones(mock_repository, limit=3).

Comment on lines +170 to +179
def test_releases_method(self):
"""Test releases method resolution."""
mock_repository = Mock()
mock_releases = Mock()
mock_releases.order_by.return_value.__getitem__ = Mock(return_value=[])
mock_repository.published_releases = mock_releases

RepositoryNode.releases(mock_repository)
mock_releases.order_by.assert_called_with("-published_at")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Also assert the releases slice limit.

Covers [:RECENT_RELEASES_LIMIT] to guard limit regressions.

 RepositoryNode.releases(mock_repository)
 mock_releases.order_by.assert_called_with("-published_at")
+mock_releases.order_by.return_value.__getitem__.assert_called_with(
+    slice(None, RECENT_RELEASES_LIMIT)
+)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_releases_method(self):
"""Test releases method resolution."""
mock_repository = Mock()
mock_releases = Mock()
mock_releases.order_by.return_value.__getitem__ = Mock(return_value=[])
mock_repository.published_releases = mock_releases
RepositoryNode.releases(mock_repository)
mock_releases.order_by.assert_called_with("-published_at")
def test_releases_method(self):
"""Test releases method resolution."""
mock_repository = Mock()
mock_releases = Mock()
mock_releases.order_by.return_value.__getitem__ = Mock(return_value=[])
mock_repository.published_releases = mock_releases
RepositoryNode.releases(mock_repository)
mock_releases.order_by.assert_called_with("-published_at")
mock_releases.order_by.return_value.__getitem__.assert_called_with(
slice(None, RECENT_RELEASES_LIMIT)
)
🤖 Prompt for AI Agents
In backend/tests/apps/github/api/internal/nodes/repository_test.py around lines
170 to 179, add an assertion that the releases slice limit is applied: after
asserting mock_releases.order_by was called with "-published_at", assert that
mock_releases.order_by.return_value.__getitem__ was called with the slice up to
the RECENT_RELEASES_LIMIT (i.e. slice(None,
RepositoryNode.RECENT_RELEASES_LIMIT, None) or equivalent
[:RECENT_RELEASES_LIMIT]); this ensures the test covers the limit and prevents
regressions.

@trucodd trucodd marked this pull request as ready for review August 11, 2025 05:38
@sonarqubecloud
Copy link

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
backend/tests/apps/github/api/internal/nodes/repository_test.py (4)

9-11: Import resolver limit constants to assert slice sizes explicitly.

Brings the test in sync with the implementation and prevents regressions if limits change.

-from apps.github.api.internal.nodes.repository import RepositoryNode
+from apps.github.api.internal.nodes.repository import (
+    RepositoryNode,
+    RECENT_ISSUES_LIMIT,
+    RECENT_RELEASES_LIMIT,
+)

103-115: Also assert issues slice limit is applied.

Ensures [:RECENT_ISSUES_LIMIT] is enforced, not just the queryset chaining.

         RepositoryNode.issues(mock_repository)
         mock_issues.select_related.assert_called_with("author")
         mock_issues.select_related.return_value.order_by.assert_called_with("-created_at")
+        mock_issues.select_related.return_value.order_by.return_value.__getitem__.assert_called_with(
+            slice(None, RECENT_ISSUES_LIMIT)
+        )

171-180: Also assert releases slice limit is applied.

Covers [:RECENT_RELEASES_LIMIT] to guard limit regressions.

         RepositoryNode.releases(mock_repository)
         mock_releases.order_by.assert_called_with("-published_at")
+        mock_releases.order_by.return_value.__getitem__.assert_called_with(
+            slice(None, RECENT_RELEASES_LIMIT)
+        )

158-170: Assert recent_milestones respects the provided limit.

Confirms slicing is applied with the passed limit.

         RepositoryNode.recent_milestones(mock_repository, limit=3)
         mock_milestones.select_related.assert_called_with("repository")
         mock_milestones.select_related.return_value.order_by.assert_called_with("-created_at")
+        mock_milestones.select_related.return_value.order_by.return_value.__getitem__.assert_called_with(
+            slice(None, 3)
+        )
🧹 Nitpick comments (8)
backend/tests/apps/github/api/internal/nodes/milestone_test.py (2)

16-32: Make field assertions resilient to future schema growth (use subset).

Asserting exact equality on field names is brittle. Prefer subset to guard against harmless additions.

-        assert field_names == expected_field_names
+        assert expected_field_names.issubset(field_names)

73-90: Add a rounding edge-case to lock the 2-decimal behavior.

Progress is rounded to 2 decimals in production. Add a fractional case to prevent regressions in rounding precision.

@@
     def test_progress_without_issues(self):
         """Test progress calculation without issues."""
         mock_milestone = Mock()
         mock_milestone.closed_issues_count = 0
         mock_milestone.open_issues_count = 0

         result = MilestoneNode.progress(mock_milestone)
         assert result == pytest.approx(0.0)
+
+    def test_progress_rounding_two_decimals(self):
+        """Test progress rounding to two decimals."""
+        mock_milestone = Mock()
+        mock_milestone.closed_issues_count = 1
+        mock_milestone.open_issues_count = 6  # 1 / 7 = 14.2857...
+
+        result = MilestoneNode.progress(mock_milestone)
+        assert result == pytest.approx(14.29)
backend/tests/apps/github/api/internal/nodes/organization_test.py (5)

15-33: Prefer subset check for field names to avoid brittle failures.

Exact equality will fail if harmless fields are added. Using subset balances strictness and flexibility.

-        assert field_names == expected_field_names
+        assert expected_field_names.issubset(field_names)

50-52: Patch where the symbol is looked up (module-under-test) to reduce coupling.

Patching the model class attribute works, but patching the names as imported in the module under test is usually safer against import refactors.

-    @patch("apps.github.models.repository.Repository.objects")
-    @patch("apps.github.models.repository_contributor.RepositoryContributor.objects")
+    @patch("apps.github.api.internal.nodes.organization.Repository.objects")
+    @patch("apps.github.api.internal.nodes.organization.RepositoryContributor.objects")

83-85: Apply the same patch-target refinement here.

-    @patch("apps.github.models.repository.Repository.objects")
-    @patch("apps.github.models.repository_contributor.RepositoryContributor.objects")
+    @patch("apps.github.api.internal.nodes.organization.Repository.objects")
+    @patch("apps.github.api.internal.nodes.organization.RepositoryContributor.objects")

69-82: Also assert repository and contributor queryset filters were invoked with expected args.

Strengthens the test by verifying the key query filters.

         # Call stats method
         result = OrganizationNode.stats(mock_organization)

         # Verify result
         assert isinstance(result, OrganizationStatsNode)
         assert result.total_repositories == 5
         assert result.total_contributors == 15
         assert result.total_stars == 100
         assert result.total_forks == 50
         assert result.total_issues == 25
+        # Verify filtering arguments
+        mock_repository_objects.filter.assert_called_with(organization=mock_organization)
+        mock_repo_contributor_objects.filter.assert_called_with(
+            repository__in=mock_repositories
+        )

107-117: Mirror filter assertions in the None-aggregates case as well.

Ensures we validate query shapes in both branches.

         # Verify result with default values
         assert isinstance(result, OrganizationStatsNode)
         assert result.total_repositories == 3
         assert result.total_contributors == 8
         assert result.total_stars == 0
         assert result.total_forks == 0
         assert result.total_issues == 0
+        mock_repository_objects.filter.assert_called_with(organization=mock_organization)
+        mock_repo_contributor_objects.filter.assert_called_with(
+            repository__in=mock_repositories
+        )
backend/tests/apps/github/api/internal/nodes/repository_test.py (1)

116-123: Avoid order brittleness in languages; assert as a set (or sort).

Ordering can change if the implementation sorts by bytes or key; assert membership instead.

-        result = RepositoryNode.languages(mock_repository)
-        assert result == ["Python", "JavaScript"]
+        result = RepositoryNode.languages(mock_repository)
+        assert set(result) == {"Python", "JavaScript"}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0b2432a and d439947.

📒 Files selected for processing (6)
  • backend/tests/apps/github/api/internal/nodes/issue_test.py (2 hunks)
  • backend/tests/apps/github/api/internal/nodes/milestone_test.py (2 hunks)
  • backend/tests/apps/github/api/internal/nodes/organization_test.py (3 hunks)
  • backend/tests/apps/github/api/internal/nodes/pull_request_test.py (1 hunks)
  • backend/tests/apps/github/api/internal/nodes/release_test.py (2 hunks)
  • backend/tests/apps/github/api/internal/nodes/repository_test.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/tests/apps/github/api/internal/nodes/issue_test.py
  • backend/tests/apps/github/api/internal/nodes/release_test.py
  • backend/tests/apps/github/api/internal/nodes/pull_request_test.py
🧰 Additional context used
🧬 Code Graph Analysis (2)
backend/tests/apps/github/api/internal/nodes/milestone_test.py (2)
backend/apps/github/api/internal/nodes/milestone.py (5)
  • author (26-28)
  • MilestoneNode (22-50)
  • organization_name (31-37)
  • progress (40-45)
  • repository_name (48-50)
backend/apps/github/api/internal/nodes/pull_request.py (3)
  • author (21-23)
  • organization_name (26-32)
  • repository_name (35-37)
backend/tests/apps/github/api/internal/nodes/organization_test.py (1)
backend/apps/github/api/internal/nodes/organization.py (3)
  • stats (43-71)
  • OrganizationStatsNode (13-20)
  • url (74-76)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests
  • GitHub Check: CodeQL (python)
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (6)
backend/tests/apps/github/api/internal/nodes/milestone_test.py (2)

34-42: LGTM: direct resolver call pattern is clear and correct.

Good use of an instance mock to exercise the resolver without constructing the whole node.


43-54: LGTM: org login resolution path is covered.

Covers repository.organization.login happy path accurately.

backend/tests/apps/github/api/internal/nodes/organization_test.py (3)

35-49: LGTM: schema-level type checks for stats and url fields.

Good sanity checks on field presence and declared types.


118-125: LGTM: url resolver directly returns backing value.


141-155: LGTM: OrganizationStatsNode construction assertions are clear.

backend/tests/apps/github/api/internal/nodes/repository_test.py (1)

98-101: LGTM: url field type check is appropriate.

Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@arkid15r arkid15r added this pull request to the merge queue Aug 15, 2025
Merged via the queue into OWASP:main with commit 91c5409 Aug 15, 2025
24 checks passed
OUMIMANDAL pushed a commit to OUMIMANDAL/Nest-milestones that referenced this pull request Aug 28, 2025
OWASP#2042)

* test- inmproved test coverage of github internal nodes more than 80% updated

* Update code

---------

Co-authored-by: Arkadii Yakovets <[email protected]>
@coderabbitai coderabbitai bot mentioned this pull request Aug 30, 2025
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve backend/apps/github/api/internal/nodes test coverage

2 participants