Skip to content

Conversation

@kart-u
Copy link
Contributor

@kart-u kart-u commented Jan 4, 2026

Proposed change

Resolves #2872

Added tests for app/mentorship
Left out tests for backend/apps/mentorship/api/internal/mutations intentionally due some ongoing work on permissions -- #3101 #3185 (waiting for their completion)
coverage2

Checklist

  • Required: I read and followed the contributing guidelines
  • Required: I ran make check-test locally and all tests passed
  • I used AI for code, documentation, or tests in this PR

@kart-u kart-u requested review from arkid15r and kasya as code owners January 4, 2026 20:27
@kart-u kart-u marked this pull request as draft January 4, 2026 20:27
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 4, 2026

Walkthrough

Adds an Issue re-export to GitHub models, removes the mentorship coverage exclusion, and introduces numerous new unit tests across the mentorship app (API nodes, queries, management commands, and models).

Changes

Cohort / File(s) Change Summary
GitHub models re-export
\backend/apps/github/models/init.py``
Re-exported Issue from .issue, expanding the module's public exports.
Coverage config
\backend/pyproject.toml``
Removed mentorship/* from pytest coverage omit list so mentorship code is included in coverage reports.
Mentorship API node tests
\backend/tests/apps/mentorship/api/internal/nodes/api_internal_enum_test.py`, `backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentee_test.py`, `backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py`, `backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py`, `backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py``
Added unit tests for node field resolvers, input types, fake node fixtures, and mocked queryset interactions.
Mentorship API query tests
\backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py`, `backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py`, `backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py``
Added comprehensive tests for query logic (is_mentor, get_mentee_details, get_mentee_module_issues, get_module(s), get_program, my_programs) with heavy ORM mocking, pagination, and error-path assertions.
Management command tests
\backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py`, `backend/tests/apps/mentorship/management/commands/mentorship_sync_module_issues_test.py`, `backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py``
Added tests exercising management command control flow, queryset behaviors, atomic transactions, issue/task linking, and stdout/stderr messaging.
Mentorship model tests
\backend/tests/apps/mentorship/model/experience_level_test.py`, `backend/tests/apps/mentorship/model/mentee_test.py`, `backend/tests/apps/mentorship/model/mentor_module_test.py`, `backend/tests/apps/mentorship/model/mentor_test.py`, `backend/tests/apps/mentorship/model/module_test.py`, `backend/tests/apps/mentorship/model/program_test.py`, `backend/tests/apps/mentorship/model/task_level_test.py`, `backend/tests/apps/mentorship/model/task_test.py``
Added unit tests for model str implementations, enum/value constants, and simple behaviors using MagicMock-based pure-unit tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • kasya
  • arkid15r
🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning One out-of-scope change detected: the public export of Issue in backend/apps/github/models/init.py is unrelated to the mentorship testing objectives and should not be included in this PR. Remove the Issue export addition from backend/apps/github/models/init.py as it is unrelated to the mentorship test coverage objectives. This change should be addressed in a separate PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 58.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding improved testing for the backend/app/mentorship module. It is specific and directly related to the changeset.
Linked Issues check ✅ Passed The PR fully meets the requirements of issue #2872: test files were added for backend/mentorship/* to improve coverage, and the pyproject.toml exclusion was removed to include mentorship files in coverage analysis.
Description check ✅ Passed The PR description clearly describes the changeset: adding comprehensive tests for backend/apps/mentorship, explicitly mentioning omitted mutations tests due to ongoing permissions work.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • 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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 11

🧹 Nitpick comments (10)
backend/apps/github/models/__init__.py (1)

3-9: Consider maintaining alphabetical order for imports.

The new Issue import breaks the alphabetical ordering of the existing imports. For consistency:

🔎 Proposed fix
 from .comment import Comment
 from .commit import Commit
+from .issue import Issue
 from .label import Label
 from .milestone import Milestone
 from .pull_request import PullRequest
 from .user import User
-from .issue import Issue
backend/tests/apps/mentorship/model/test_task_level.py (1)

17-26: Second test is redundant and has a misleading name.

This test is identical in behavior to the first test. Setting module.__str__.return_value = "ignored-str" has no effect because TaskLevel.__str__ accesses self.module.name directly, not str(self.module). The test name suggests it verifies special handling of custom __str__, but there's no such logic in the implementation.

Consider either removing this test or renaming it to clarify what scenario it actually covers.

backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py (1)

10-20: Consider extracting shared test utilities to a conftest.py.

The make_qs helper function is duplicated in test_mentorship_sync_module_issues.py and test_mentorship_sync_issue_levels.py. For DRY, consider moving it to a shared conftest.py in the backend/tests/apps/mentorship/ directory.

backend/tests/apps/mentorship/model/test_start_end_range.py (2)

2-2: Remove unused import.

The patch import is not used in this test file.

🔎 Proposed fix
-from unittest.mock import MagicMock, patch
+from unittest.mock import MagicMock

8-18: Test doesn't validate meaningful behavior.

This test merely assigns attributes to a mock and reads them back, which doesn't actually verify StartEndRange model behavior. Since StartEndRange is an abstract model with only field definitions, consider either removing this test or converting it to an integration test that validates field behavior in a concrete model context.

Additional cleanup

Line 15 is redundant and can be removed:

     mock_instance = MagicMock(spec=StartEndRange)
     mock_instance.started_at = start_time
     mock_instance.ended_at = end_time
-    instance = mock_instance

-    assert instance.started_at == start_time
-    assert instance.ended_at == end_time
+    assert mock_instance.started_at == start_time
+    assert mock_instance.ended_at == end_time
backend/tests/apps/mentorship/model/test_mentor_module.py (1)

3-4: Consider consolidating imports.

The two imports from apps.mentorship.models can be combined into a single line.

🔎 Proposed fix
-from apps.mentorship.models import MentorModule
-from apps.mentorship.models import Mentor, Module
+from apps.mentorship.models import Mentor, MentorModule, Module
backend/tests/apps/mentorship/model/test_issue_user_interest.py (2)

13-43: Consider integration tests in addition to mocked unit tests.

While mocking objects.create is valid for unit testing, it doesn't actually validate model behavior, database constraints, or Django ORM interactions. The test verifies that the mock was called correctly, not that the model works correctly.

For comprehensive coverage, consider adding integration tests using pytest-django's database fixtures that create actual model instances.

Example integration test approach
@pytest.mark.django_db
def test_create_issue_user_interest_integration(self):
    # Create real instances and test actual database behavior
    user = User.objects.create(login="testuser")
    issue = Issue.objects.create(title="Test Issue")
    module = Module.objects.create(name="Test Module")
    
    interest = IssueUserInterest.objects.create(
        user=user,
        issue=issue,
        module=module,
    )
    
    assert interest.user == user
    assert interest.issue == issue
    assert interest.module == module
    assert "testuser" in str(interest)

46-68: Use Django's IntegrityError for more realistic testing.

The test defines a custom MockIntegrityError instead of using Django's actual IntegrityError. This makes the test less realistic and doesn't verify that Django would actually raise the correct exception type.

🔎 Proposed fix
+from django.db import IntegrityError
+
     @patch("apps.mentorship.models.IssueUserInterest.objects.create")
     def test_unique_together_constraint(self, mock_create):
-        class MockIntegrityError(Exception):
-            pass
-
         mock_create.side_effect = [
             MagicMock(),
-            MockIntegrityError("Mock Integrity Error"),
+            IntegrityError("UNIQUE constraint failed"),
         ]

         IssueUserInterest.objects.create(
             user=self.user,
             issue=self.issue,
             module=self.module,
         )

-        with pytest.raises(MockIntegrityError):
+        with pytest.raises(IntegrityError):
             IssueUserInterest.objects.create(
                 user=self.user,
                 issue=self.issue,
                 module=self.module,
             )

         assert mock_create.call_count == 2
backend/tests/apps/mentorship/model/test_program.py (1)

6-10: Remove unused code.

MockIntegrityError is defined but never used in this test file. The same applies to the Mentor import on line 9.

🔎 Proposed cleanup
-class MockIntegrityError(Exception):
-    pass
-
-from apps.mentorship.models import Program, Mentor
+from apps.mentorship.models import Program
 from apps.github.models import User as GithubUser
backend/tests/apps/mentorship/model/test_module.py (1)

6-8: Remove unused imports.

PublishedModuleManager and Issue are imported but never used in this test file.

🔎 Proposed cleanup
 from apps.mentorship.models import Module
-from apps.mentorship.models.managers import PublishedModuleManager
 from apps.mentorship.models import Program, Mentor
-from apps.github.models import Issue, User as GithubUser
+from apps.github.models import User as GithubUser
 from apps.owasp.models import Project
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a34336 and 5d5da8b.

📒 Files selected for processing (16)
  • backend/apps/github/models/__init__.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
  • backend/tests/apps/mentorship/model/test_experience_level.py
  • backend/tests/apps/mentorship/model/test_issue_user_interest.py
  • backend/tests/apps/mentorship/model/test_mentee.py
  • backend/tests/apps/mentorship/model/test_mentee_module.py
  • backend/tests/apps/mentorship/model/test_mentee_program.py
  • backend/tests/apps/mentorship/model/test_mentor.py
  • backend/tests/apps/mentorship/model/test_mentor_module.py
  • backend/tests/apps/mentorship/model/test_module.py
  • backend/tests/apps/mentorship/model/test_program.py
  • backend/tests/apps/mentorship/model/test_start_end_range.py
  • backend/tests/apps/mentorship/model/test_task.py
  • backend/tests/apps/mentorship/model/test_task_level.py
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/model/test_program.py
  • backend/tests/apps/mentorship/model/test_task.py
  • backend/tests/apps/mentorship/model/test_mentee_program.py
  • backend/tests/apps/mentorship/model/test_mentor.py
  • backend/tests/apps/mentorship/model/test_issue_user_interest.py
  • backend/tests/apps/mentorship/model/test_task_level.py
  • backend/tests/apps/mentorship/model/test_mentee.py
  • backend/apps/github/models/__init__.py
  • backend/tests/apps/mentorship/model/test_module.py
  • backend/tests/apps/mentorship/model/test_mentee_module.py
  • backend/tests/apps/mentorship/model/test_experience_level.py
  • backend/tests/apps/mentorship/model/test_mentor_module.py
  • backend/tests/apps/mentorship/model/test_start_end_range.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/model/test_program.py
  • backend/tests/apps/mentorship/model/test_task.py
  • backend/tests/apps/mentorship/model/test_mentee_program.py
  • backend/tests/apps/mentorship/model/test_mentor.py
  • backend/tests/apps/mentorship/model/test_issue_user_interest.py
  • backend/tests/apps/mentorship/model/test_task_level.py
  • backend/tests/apps/mentorship/model/test_mentee.py
  • backend/apps/github/models/__init__.py
  • backend/tests/apps/mentorship/model/test_module.py
  • backend/tests/apps/mentorship/model/test_mentee_module.py
  • backend/tests/apps/mentorship/model/test_experience_level.py
  • backend/tests/apps/mentorship/model/test_mentor_module.py
  • backend/tests/apps/mentorship/model/test_start_end_range.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 2000
File: backend/apps/mentorship/management/commands/sync_module_issues.py:109-145
Timestamp: 2025-08-17T11:55:55.990Z
Learning: In the OWASP/Nest mentorship system, tasks are designed to be assigned to only one assignee per issue, even if GitHub issues can have multiple assignees. The sync_module_issues command correctly uses issue.assignees.first() to create one task per issue for the first assignee only.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py
🧬 Code graph analysis (16)
backend/tests/apps/mentorship/model/test_program.py (1)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/tests/apps/mentorship/model/test_task.py (1)
backend/apps/mentorship/models/task.py (1)
  • Task (10-75)
backend/tests/apps/mentorship/model/test_mentee_program.py (2)
backend/apps/mentorship/models/mentee.py (1)
  • Mentee (11-49)
backend/apps/mentorship/models/mentee_program.py (1)
  • MenteeProgram (11-39)
backend/tests/apps/mentorship/model/test_mentor.py (2)
backend/apps/mentorship/models/mentor.py (1)
  • Mentor (11-40)
backend/tests/apps/mentorship/model/test_mentee.py (1)
  • test_str_returns_github_login (8-14)
backend/tests/apps/mentorship/model/test_issue_user_interest.py (3)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-36)
backend/tests/apps/mentorship/model/test_module.py (1)
  • MockIntegrityError (12-13)
backend/tests/apps/mentorship/model/test_program.py (1)
  • MockIntegrityError (6-7)
backend/tests/apps/mentorship/model/test_task_level.py (1)
backend/apps/mentorship/models/task_level.py (1)
  • TaskLevel (8-61)
backend/tests/apps/mentorship/model/test_mentee.py (3)
backend/apps/mentorship/models/mentee.py (1)
  • Mentee (11-49)
backend/tests/apps/mentorship/model/test_mentor.py (1)
  • test_str_returns_github_login (8-14)
backend/apps/mentorship/api/internal/nodes/mentor.py (1)
  • login (23-25)
backend/apps/github/models/__init__.py (3)
backend/tests/apps/github/models/issue_test.py (1)
  • issue (26-28)
backend/apps/github/models/issue.py (1)
  • Issue (19-235)
frontend/src/types/issue.ts (1)
  • Issue (4-24)
backend/tests/apps/mentorship/model/test_module.py (4)
backend/apps/mentorship/models/managers/module.py (1)
  • PublishedModuleManager (8-13)
backend/tests/apps/mentorship/model/test_issue_user_interest.py (1)
  • MockIntegrityError (47-48)
backend/tests/apps/mentorship/model/test_program.py (1)
  • MockIntegrityError (6-7)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/tests/apps/mentorship/model/test_mentee_module.py (1)
backend/apps/mentorship/models/mentee_module.py (1)
  • MenteeModule (11-45)
backend/tests/apps/mentorship/model/test_experience_level.py (1)
backend/apps/mentorship/models/common/experience_level.py (2)
  • ExperienceLevel (6-25)
  • ExperienceLevelChoices (12-18)
backend/tests/apps/mentorship/model/test_mentor_module.py (2)
backend/apps/mentorship/models/mentor_module.py (1)
  • MentorModule (11-42)
backend/apps/mentorship/models/mentor.py (1)
  • Mentor (11-40)
backend/tests/apps/mentorship/model/test_start_end_range.py (1)
backend/apps/mentorship/models/common/start_end_range.py (1)
  • StartEndRange (6-13)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py (1)
backend/apps/mentorship/management/commands/mentorship_sync_module_issues.py (5)
  • _extract_repo_full_name (28-40)
  • _get_status (42-50)
  • _get_last_assigned_date (52-74)
  • _build_repo_label_to_issue_map (76-92)
  • _process_module (94-203)
backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py (1)
backend/apps/mentorship/management/commands/mentorship_update_comments.py (3)
  • process_mentorship_modules (31-47)
  • process_module (49-81)
  • process_issue_interests (83-152)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py (1)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py (2)
  • command (22-30)
  • make_qs (12-18)
🪛 Ruff (0.14.10)
backend/tests/apps/mentorship/model/test_program.py

1-1: File backend/tests/apps/mentorship/model/test_program.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


9-9: Module level import not at top of file

(E402)


10-10: Module level import not at top of file

(E402)


16-16: Unused lambda argument: self

(ARG005)


23-23: datetime.datetime() called without a tzinfo argument

(DTZ001)


24-24: datetime.datetime() called without a tzinfo argument

(DTZ001)


36-36: datetime.datetime() called without a tzinfo argument

(DTZ001)


37-37: datetime.datetime() called without a tzinfo argument

(DTZ001)


47-47: datetime.datetime() called without a tzinfo argument

(DTZ001)


48-48: datetime.datetime() called without a tzinfo argument

(DTZ001)


58-58: datetime.datetime() called without a tzinfo argument

(DTZ001)


59-59: datetime.datetime() called without a tzinfo argument

(DTZ001)


74-74: No newline at end of file

Add trailing newline

(W292)

backend/tests/apps/mentorship/model/test_task.py

1-1: File backend/tests/apps/mentorship/model/test_task.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/model/test_mentee_program.py

1-1: File backend/tests/apps/mentorship/model/test_mentee_program.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


19-19: Unused lambda argument: self

(ARG005)


25-25: Unused lambda argument: self

(ARG005)


30-30: datetime.datetime() called without a tzinfo argument

(DTZ001)


31-31: datetime.datetime() called without a tzinfo argument

(DTZ001)


75-75: datetime.datetime() called without a tzinfo argument

(DTZ001)


82-82: datetime.datetime() called without a tzinfo argument

(DTZ001)

backend/tests/apps/mentorship/model/test_mentor.py

1-1: File backend/tests/apps/mentorship/model/test_mentor.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/model/test_issue_user_interest.py

1-1: File backend/tests/apps/mentorship/model/test_issue_user_interest.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/model/test_task_level.py

1-1: File backend/tests/apps/mentorship/model/test_task_level.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/model/test_mentee.py

1-1: File backend/tests/apps/mentorship/model/test_mentee.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/model/test_module.py

1-1: File backend/tests/apps/mentorship/model/test_module.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


23-23: datetime.datetime() called without a tzinfo argument

(DTZ001)


24-24: datetime.datetime() called without a tzinfo argument

(DTZ001)


48-48: datetime.datetime() called without a tzinfo argument

(DTZ001)


49-49: datetime.datetime() called without a tzinfo argument

(DTZ001)


64-64: datetime.datetime() called without a tzinfo argument

(DTZ001)


65-65: datetime.datetime() called without a tzinfo argument

(DTZ001)


78-78: datetime.datetime() called without a tzinfo argument

(DTZ001)


79-79: datetime.datetime() called without a tzinfo argument

(DTZ001)


92-95: 1 blank line required between summary line and description

(D205)


92-95: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


107-111: 1 blank line required between summary line and description

(D205)


107-111: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


116-116: datetime.datetime() called without a tzinfo argument

(DTZ001)


117-117: datetime.datetime() called without a tzinfo argument

(DTZ001)


127-127: Unused function argument: args

(ARG001)


127-127: Unused function argument: kwargs

(ARG001)


141-141: datetime.datetime() called without a tzinfo argument

(DTZ001)


142-142: datetime.datetime() called without a tzinfo argument

(DTZ001)


144-144: datetime.datetime() called without a tzinfo argument

(DTZ001)


145-145: datetime.datetime() called without a tzinfo argument

(DTZ001)


156-156: No newline at end of file

Add trailing newline

(W292)

backend/tests/apps/mentorship/model/test_mentee_module.py

1-1: File backend/tests/apps/mentorship/model/test_mentee_module.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


16-16: Unused lambda argument: self

(ARG005)


18-18: Unused lambda argument: self

(ARG005)


22-22: datetime.datetime() called without a tzinfo argument

(DTZ001)


23-23: datetime.datetime() called without a tzinfo argument

(DTZ001)


62-62: datetime.datetime() called without a tzinfo argument

(DTZ001)


68-68: datetime.datetime() called without a tzinfo argument

(DTZ001)

backend/tests/apps/mentorship/model/test_experience_level.py

1-1: File backend/tests/apps/mentorship/model/test_experience_level.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/model/test_mentor_module.py

1-1: File backend/tests/apps/mentorship/model/test_mentor_module.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/model/test_start_end_range.py

1-1: File backend/tests/apps/mentorship/model/test_start_end_range.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


9-9: datetime.datetime() called without a tzinfo argument

(DTZ001)


10-10: datetime.datetime() called without a tzinfo argument

(DTZ001)

backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py

1-1: File backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


12-12: Boolean default positional argument in function definition

(FBT002)


40-40: Wrong type passed to first argument of pytest.mark.parametrize; expected tuple

Use a tuple for the first argument

(PT006)


56-56: Multiple statements on one line (semicolon)

(E702)


69-69: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


70-70: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


71-71: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


72-72: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


76-76: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


97-97: Variable MockIssue in function should be lowercase

(N806)


115-115: Argument name MockIssue should be lowercase

(N803)


115-115: Argument name MockTask should be lowercase

(N803)


116-116: Multiple statements on one line (semicolon)

(E702)


116-116: Multiple statements on one line (semicolon)

(E702)


121-121: Multiple statements on one line (semicolon)

(E702)


121-121: Multiple statements on one line (semicolon)

(E702)


128-128: Multiple statements on one line (semicolon)

(E702)


152-152: Local variable num_linked is assigned to but never used

Remove assignment to unused variable num_linked

(F841)


185-185: Unused function argument: command

(ARG001)


185-185: Test function parameter mock_module has default argument

Remove default argument

(PT028)


185-185: Unused function argument: mock_repository

(ARG001)


185-185: Test function parameter mock_repository has default argument

Remove default argument

(PT028)


186-186: Multiple statements on one line (semicolon)

(E702)


186-186: Multiple statements on one line (semicolon)

(E702)


193-193: Variable MockTask in function should be lowercase

(N806)

backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py

1-1: File backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


10-10: Boolean default positional argument in function definition

(FBT002)


73-73: Argument name MockModule should be lowercase

(N803)


81-81: Argument name MockModule should be lowercase

(N803)


93-93: Argument name MockIssue should be lowercase

(N803)


116-116: Argument name MockIssueUserInterest should be lowercase

(N803)


145-145: Argument name MockIssueUserInterest should be lowercase

(N803)


174-174: Argument name MockIssueUserInterest should be lowercase

(N803)


210-210: Argument name MockIssueUserInterest should be lowercase

(N803)


236-236: Argument name MockIssueUserInterest should be lowercase

(N803)

backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py

1-1: File backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


17-17: Boolean default positional argument in function definition

(FBT002)


18-18: First line of docstring should be in imperative mood: "Helper: return a queryset-like MagicMock that is iterable and supports .exists()."

(D401)


27-27: Argument name MockTaskLevel should be lowercase

(N803)


28-28: No blank lines allowed after function docstring (found 1)

Remove blank line(s) after function docstring

(D202)


39-39: Argument name MockTaskLevel should be lowercase

(N803)


39-39: Argument name MockIssue should be lowercase

(N803)


40-40: No blank lines allowed after function docstring (found 1)

Remove blank line(s) after function docstring

(D202)


52-52: Multiple statements on one line (semicolon)

(E702)


53-53: Multiple statements on one line (semicolon)

(E702)


54-54: Multiple statements on one line (semicolon)

(E702)


97-97: Argument name MockTaskLevel should be lowercase

(N803)


97-97: Argument name MockIssue should be lowercase

(N803)


106-106: Multiple statements on one line (semicolon)

(E702)

🔇 Additional comments (15)
backend/tests/apps/mentorship/model/test_experience_level.py (1)

1-9: LGTM!

The test correctly validates the string values of the ExperienceLevelChoices enum members. The assertions align with the source definition in experience_level.py.

backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py (4)

89-112: LGTM!

The test_process_module test correctly verifies the orchestration of issue syncing and interest processing, with appropriate mocking of dependencies.


115-141: LGTM!

The test correctly validates that new interests are created when a user comments with /interested, with proper verification of the bulk_create call and stdout output.


144-169: LGTM!

The test correctly verifies interest removal when a user's comment doesn't match interest patterns, including proper verification of the delete call and warning message.


235-280: LGTM!

The multi-user test comprehensively validates mixed scenarios where some users express interest (user2, user3) while an existing interest is removed (user1). The assertions correctly verify the created and removed interests.

backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py (3)

39-52: LGTM!

Good use of pytest.mark.parametrize to test multiple URL formats and edge cases for repository name extraction. The test cases cover valid GitHub URLs, invalid hosts, malformed URLs, and null input.


64-86: LGTM!

Comprehensive test for _get_last_assigned_date covering:

  • Finding the latest assigned event for a specific user
  • Not finding events for the target user
  • Handling GithubException with stderr output

113-182: LGTM!

The test_process_module_links_and_creates_tasks test thoroughly validates:

  • Linking issues to modules via module.issues.set()
  • Creating tasks with correct status mapping (IN_PROGRESS for open+assigned, COMPLETED for closed)
  • Appropriate stdout success messages
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py (3)

26-34: LGTM!

Test correctly verifies early exit behavior when no TaskLevel objects exist, with appropriate warning message.


37-92: LGTM!

Comprehensive test for issue level updates:

  • Correctly sets up multiple issues with different label/level combinations
  • Validates that issues are updated to matching levels
  • Verifies bulk_update is called with the correct subset of changed issues
  • Confirms success message output

95-119: LGTM!

Test correctly verifies that bulk_update is not called when all issues already have the correct level, with appropriate "up-to-date" message.

backend/tests/apps/mentorship/model/test_mentee.py (1)

1-14: LGTM!

The test correctly validates Mentee.__str__ returns the GitHub login. The approach is consistent with the existing test_mentor.py test pattern.

backend/tests/apps/mentorship/model/test_mentor.py (1)

7-14: LGTM!

The test correctly validates the Mentor.str implementation using appropriate mocking. The test is focused and verifies the expected string representation.

backend/tests/apps/mentorship/model/test_mentor_module.py (1)

7-19: LGTM!

The test properly validates the MentorModule.str implementation by mocking the mentor and module components and verifying the expected format.

backend/tests/apps/mentorship/model/test_task.py (1)

7-37: LGTM!

The test suite provides good coverage of Task model behavior:

  • Both assigned and unassigned scenarios for string representation
  • Status constants validation

The implementation is clean and well-structured.

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: 2

♻️ Duplicate comments (2)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py (1)

185-208: Test function has incorrect signature with unused default parameters.

The function signature includes an unused command fixture parameter and invalid default arguments mock_module=None and mock_repository=None. Additionally, line 198 creates a new Command() instance instead of using the provided fixture.

🔎 Proposed fix
-def test_process_module_no_matches(command, mock_module=None, mock_repository=None):
+def test_process_module_no_matches():
     mock_repo = MagicMock(); mock_repo.id = 7; mock_repo.name = "r"
     mock_module = MagicMock()
backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py (1)

75-81: Remove test for non-existent command behavior.

This test mocks .exclude().select_related() and expects the message "No published mentorship modules with labels found. Exiting.", but the actual process_mentorship_modules implementation only checks published_modules.exists() and iterates over all published modules without any .exclude() or label-filtering logic. This test will never execute the asserted code path.

Based on the actual command implementation in the relevant code snippets.

🧹 Nitpick comments (2)
backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py (2)

5-5: Remove unused import.

The datetime as dt import is unused throughout the file.

🔎 Proposed fix
-import datetime as dt

159-185: Test validates interest removal correctly.

The test verifies that an existing interest is removed when a user posts a regular comment without interest patterns. Note that this test is similar to test_process_issue_interests_remove_interest (lines 134-156) and may provide redundant coverage.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d5da8b and 13de343.

📒 Files selected for processing (2)
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 2000
File: backend/apps/mentorship/management/commands/sync_module_issues.py:109-145
Timestamp: 2025-08-17T11:55:55.990Z
Learning: In the OWASP/Nest mentorship system, tasks are designed to be assigned to only one assignee per issue, even if GitHub issues can have multiple assignees. The sync_module_issues command correctly uses issue.assignees.first() to create one task per issue for the first assignee only.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
🧬 Code graph analysis (2)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py (1)
backend/apps/mentorship/management/commands/mentorship_sync_module_issues.py (5)
  • _extract_repo_full_name (28-40)
  • _get_status (42-50)
  • _get_last_assigned_date (52-74)
  • _build_repo_label_to_issue_map (76-92)
  • _process_module (94-203)
backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py (1)
backend/apps/mentorship/management/commands/mentorship_update_comments.py (3)
  • process_mentorship_modules (31-47)
  • process_module (49-81)
  • process_issue_interests (83-152)
🪛 Ruff (0.14.10)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py

1-1: File backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


12-12: Boolean default positional argument in function definition

(FBT002)


40-40: Wrong type passed to first argument of pytest.mark.parametrize; expected tuple

Use a tuple for the first argument

(PT006)


44-44: Duplicate of test case at index 0 in pytest.mark.parametrize

Remove duplicate test case

(PT014)


56-56: Multiple statements on one line (semicolon)

(E702)


69-69: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


70-70: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


71-71: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


72-72: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


76-76: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


97-97: Variable MockIssue in function should be lowercase

(N806)


115-115: Argument name MockIssue should be lowercase

(N803)


115-115: Argument name MockTask should be lowercase

(N803)


116-116: Multiple statements on one line (semicolon)

(E702)


116-116: Multiple statements on one line (semicolon)

(E702)


121-121: Multiple statements on one line (semicolon)

(E702)


121-121: Multiple statements on one line (semicolon)

(E702)


128-128: Multiple statements on one line (semicolon)

(E702)


152-152: Local variable num_linked is assigned to but never used

Remove assignment to unused variable num_linked

(F841)


185-185: Unused function argument: command

(ARG001)


185-185: Test function parameter mock_module has default argument

Remove default argument

(PT028)


185-185: Unused function argument: mock_repository

(ARG001)


185-185: Test function parameter mock_repository has default argument

Remove default argument

(PT028)


186-186: Multiple statements on one line (semicolon)

(E702)


186-186: Multiple statements on one line (semicolon)

(E702)


193-193: Variable MockTask in function should be lowercase

(N806)

backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py

1-1: File backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


10-10: Boolean default positional argument in function definition

(FBT002)


68-68: Argument name MockModule should be lowercase

(N803)


76-76: Argument name MockModule should be lowercase

(N803)


88-88: Argument name MockIssue should be lowercase

(N803)


109-109: Argument name MockIssueUserInterest should be lowercase

(N803)


135-135: Argument name MockIssueUserInterest should be lowercase

(N803)


161-161: Argument name MockIssueUserInterest should be lowercase

(N803)


189-189: Argument name MockIssueUserInterest should be lowercase

(N803)


215-215: Argument name MockIssueUserInterest should be lowercase

(N803)

🔇 Additional comments (7)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py (1)

64-86: Comprehensive test coverage for assigned date retrieval.

This test effectively covers the success path (finding the most recent assigned event), the not-found case, and exception handling with stderr reporting. The verification of stderr output on error aligns with Django management command best practices.

backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py (6)

67-72: Test correctly validates empty module scenario.

The test accurately reflects the command's behavior when no published modules exist.


84-106: Test comprehensively validates module processing orchestration.

The test correctly verifies that process_module syncs issue comments and processes interests for each issue.


108-131: Test accurately validates new interest registration.

The test correctly verifies that when a user comments with "/interested", a new IssueUserInterest is created via bulk_create.


134-156: Test correctly validates interest removal.

The test accurately verifies that when a user with an existing interest posts a comment without the interest pattern, their interest is removed.


188-211: Test accurately validates single-user multi-comment aggregation.

The test correctly verifies that multiple comments from a single user are aggregated, ensuring only one interest is created regardless of the number of comments.


214-259: Test comprehensively validates mixed multi-user scenarios.

The test thoroughly verifies the command's behavior with multiple users exhibiting different interest patterns: creating new interests for user2 and user3, while removing user1's existing interest.

@kart-u kart-u force-pushed the test_mentorship_backend branch from 13de343 to c68231e Compare January 6, 2026 19: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: 10

🤖 Fix all issues with AI Agents
In
@backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py:
- Line 61: The file ends without a trailing newline; update
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py to
add a single newline character at EOF so the final line (the assertion calling
mentee_node_no_exp.resolve_experience_level()) is terminated with a newline,
complying with PEP 8.
- Line 57: Replace the generic "# type: ignore" on the experience_level
assignment in test_api_internal_mentee.py with a specific mypy error code; since
this is ignoring assigning None to a non-Optional field, change it to "# type:
ignore[assignment]" on the experience_level=None line (preserve the comment
placement and nothing else).

In
@backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py:
- Line 68: The test file ends without a trailing newline; add a single newline
character at the end of
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py so
the final line (the assertion using mock_mentor_node_no_github_user.login()) is
followed by a newline per PEP 8/POSIX conventions.

In
@backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py:
- Line 129: The file ends without a terminating newline; add a single trailing
newline character at the end of
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py so
the file ends with a newline (preserve the existing content including the assert
UpdateProgramStatusInput.__annotations__["status"] == ProgramStatusEnum).

In @backend/tests/apps/mentorship/model/test_mentee_module.py:
- Around line 52-71: The test_unique_together_constraint currently patches
MenteeModule.objects.create and raises a MockIntegrityError, so it doesn't
verify the real DB constraint; change the test to be a Django TestCase (class
inherits from django.test.TestCase), remove the @patch and mock_create, create
the first MenteeModule via MenteeModule.objects.create(mentee=self.mentee,
module=self.module, started_at=...) using the real test DB, then assert that
creating the same record a second time raises django.db.IntegrityError (use
pytest.raises(django.db.IntegrityError) or self.assertRaises in TestCase);
ensure setUp provides valid self.mentee and self.module so the uniqueness
constraint on MenteeModule (unique_together = ("mentee", "module")) is actually
exercised.
- Around line 20-50: The test test_create_mentee_module incorrectly patches
MenteeModule.objects.create so it never exercises real model behavior; remove
the @patch decorator and call MenteeModule.objects.create directly in a Django
TestCase so the ORM and model validations run. Ensure the test class inherits
django.test.TestCase (or TransactionTestCase), create any required related
objects in setUp (e.g., self.mentee and self.module), call
MenteeModule.objects.create(…) without mocking, and assert the created instance
fields and __str__ value as before; also remove/mock-only external dependencies
if needed but do not mock the create method itself.

In @backend/tests/apps/mentorship/model/test_mentee_program.py:
- Around line 28-63: The test patches MenteeProgram.objects.create so it never
exercises the real Django model; remove the @patch decorator and the mock_create
usage, convert the test to a Django TestCase (or TransactionTestCase) that uses
the test database, ensure you create any required related objects (self.mentee,
self.program) within setUp or the test, call MenteeProgram.objects.create(...)
for a real DB record (or instantiate and save the model), then assert field
values and str(mentee_program) to validate model behavior and validations
instead of verifying mock interactions.
- Around line 65-85: The test currently mocks MenteeProgram.objects.create and
uses MockIntegrityError, so it doesn't verify the DB-level unique_together
constraint; replace the mocked test with a real DB test by converting it to a
Django TestCase (or TransactionTestCase) and create actual MenteeProgram
instances using MenteeProgram.objects.create, then assert that creating a
duplicate (same mentee and program) raises django.db.IntegrityError; remove use
of MockIntegrityError and the patch on MenteeProgram.objects.create so the real
ORM and DB constraint are exercised.

In @backend/tests/apps/mentorship/model/test_program.py:
- Around line 1-10: Move the MockIntegrityError declaration below the import
block so all import statements (datetime, MagicMock, patch, pytest, and from
apps.mentorship.models import Program, Mentor and from apps.github.models import
User as GithubUser) appear before any non-import code; specifically, relocate
the MockIntegrityError class definition to after those imports (e.g., after the
GithubUser import) to restore proper import organization.

In @backend/tests/apps/mentorship/model/test_start_end_range.py:
- Line 2: Remove the unused import by editing the import statement that
currently reads "from unittest.mock import MagicMock, patch" in the
test_start_end_range.py file and drop "patch" so only MagicMock is imported;
ensure no other references to "patch" exist in this file before committing the
change.
🧹 Nitpick comments (22)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py (1)

7-14: Consider adding cmd.style.ERROR for consistency.

The command fixture is missing the ERROR style mock present in similar fixtures across other mentorship command tests. While not required for the current tests, adding it maintains consistency:

cmd.style.ERROR = lambda x: x
backend/tests/apps/mentorship/api/internal/queries/test_api_queries_program.py (3)

36-37: Escape regex metacharacter in match pattern.

The . at the end of the pattern is a regex metacharacter that matches any single character. Use a raw string and escape it to match a literal period.

🔎 Proposed fix
-        with pytest.raises(ObjectDoesNotExist, match="Program with key 'nonexistent' not found."):
+        with pytest.raises(ObjectDoesNotExist, match=r"Program with key 'nonexistent' not found\."):

65-77: Remove unused mock configurations.

The mock configurations at lines 67-68 and 70-71 for modules.return_value.mentors.return_value... are never accessed during test execution. The my_programs implementation uses program.admins.all() to determine user role, which is correctly mocked. These dead mock setups can be removed to improve test clarity.

🔎 Proposed fix
         mock_admin_program = MagicMock(spec=Program, nest_created_at="2023-01-01", id=1)
         mock_admin_program.admins.all.return_value = [mock_mentor]
-        mock_admin_program.modules.return_value.mentors.return_value.github_user.return_value = []

         mock_mentor_program = MagicMock(spec=Program, nest_created_at="2023-01-02", id=2)
         mock_mentor_program.admins.all.return_value = []
-        mock_mentor_program.modules.return_value.mentors.return_value = [mock_mentor]

142-144: Remove unused mock configuration (same pattern).

Similar to the previous comment, lines 143-144 set up unused mock chains. The same applies to lines 169, 173, and 177 in test_my_programs_pagination.

backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py (3)

84-99: Add spaces around = in attribute assignments.

The attribute assignments are missing spaces around = (e.g., mock_module.id=1). While this works, it's inconsistent with PEP 8 and the rest of the codebase.

🔎 Proposed fix
         mock_module = MagicMock(spec=Module)
-        mock_module.id=1
+        mock_module.id = 1
         mock_module_only.return_value.get.return_value = mock_module

         mock_github_user = MagicMock(spec=GithubUser)
-        mock_github_user.login="testmentee"
-        mock_github_user.name="Test_Mentee" 
-        mock_github_user.avatar_url="url"
-        mock_github_user.bio="bio"
+        mock_github_user.login = "testmentee"
+        mock_github_user.name = "Test_Mentee"
+        mock_github_user.avatar_url = "url"
+        mock_github_user.bio = "bio"

         mock_github_user_only.return_value.get.return_value = mock_github_user

         mock_mentee = MagicMock(spec=Mentee)
-        mock_mentee.id=2
-        mock_mentee.experience_level="Mid"
-        mock_mentee.domains=["Web"]
-        mock_mentee.tags=["Python"]
+        mock_mentee.id = 2
+        mock_mentee.experience_level = "Mid"
+        mock_mentee.domains = ["Web"]
+        mock_mentee.tags = ["Python"]

348-352: Remove unused mock variables.

mock_issue1 and mock_issue3 are defined but never used. Only mock_issue2 is needed for this pagination test.

🔎 Proposed fix
-        mock_issue1 = MagicMock(id=1, number=1, title="Issue 1", state="open", created_at="", url="")
         mock_issue2 = MagicMock(id=2, number=2, title="Issue 2", state="closed", created_at="", url="")
-        mock_issue3 = MagicMock(id=3, number=3, title="Issue 3", state="open", created_at="", url="")

         mock_issues_qs_slice = [mock_issue2]

208-208: Minor style inconsistencies.

A few spacing issues:

  • Line 208: mock_prefetch:MagicMockmock_prefetch: MagicMock
  • Line 336, 386: mock_prefetch.return_value=MagicMock()mock_prefetch.return_value = MagicMock()

Also applies to: 336-336, 386-386

backend/tests/apps/mentorship/api/internal/queries/test_api_queries_module.py (2)

86-89: Escape regex metacharacter in match pattern.

The . character in the match string is a regex metacharacter. Use a raw string with escaped period for exact matching.

🔎 Proposed fix
         with pytest.raises(
-            ObjectDoesNotExist, match="Module with key 'nonexistent' under program 'program1' not found."
+            ObjectDoesNotExist, match=r"Module with key 'nonexistent' under program 'program1' not found\."
         ):

96-97: Remove trailing blank lines.

There are extra blank lines at the end of the file.

backend/tests/apps/mentorship/model/test_start_end_range.py (2)

9-10: Consider using timezone-aware datetime objects.

The datetime objects are created without timezone information. Django typically uses timezone-aware datetimes when USE_TZ=True. While this may work for mock tests, using timezone-aware datetimes is better practice.

🔎 Suggested improvement
-from datetime import datetime
+from datetime import datetime, timezone
 from unittest.mock import MagicMock, patch
 
 from apps.mentorship.models.common.start_end_range import StartEndRange
 
 
 class TestStartEndRange:
     def test_fields(self):
-        start_time = datetime(2023, 1, 1, 10, 0, 0)
-        end_time = datetime(2023, 1, 1, 11, 0, 0)
+        start_time = datetime(2023, 1, 1, 10, 0, 0, tzinfo=timezone.utc)
+        end_time = datetime(2023, 1, 1, 11, 0, 0, tzinfo=timezone.utc)

12-18: Simplify the test implementation.

Line 15 (instance = mock_instance) is redundant and can be removed. Additionally, the test could be more meaningful by testing actual model behavior rather than just mock attribute assignment.

🔎 Simplified version
-        mock_instance = MagicMock(spec=StartEndRange)
-        mock_instance.started_at = start_time
-        mock_instance.ended_at = end_time
-        instance = mock_instance
-
-        assert instance.started_at == start_time
-        assert instance.ended_at == end_time
+        instance = MagicMock(spec=StartEndRange)
+        instance.started_at = start_time
+        instance.ended_at = end_time
+
+        assert instance.started_at == start_time
+        assert instance.ended_at == end_time
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py (1)

11-24: Consider simplifying the fixture return.

The intermediate assignment to mentee_node before returning is unnecessary.

🔎 Proposed simplification
 @pytest.fixture
 def mock_mentee_node():
     """Fixture for a mock MenteeNode instance."""
-    mentee_node = MenteeNode(
+    return MenteeNode(
         id="1",
         login="testmentee",
         name="Test Mentee",
         avatar_url="https://example.com/avatar.jpg",
         bio="A test mentee",
         experience_level=ExperienceLevelEnum.BEGINNER,
         domains=["python"],
         tags=["backend"],
     )
-    return mentee_node
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py (2)

36-38: Clarify the admins mock setup.

The mock setup for admins is confusing. Line 38 reassigns mock_program.admins to mock_program.admins.all, which obscures the intent.

🔎 Clearer approach
-    # Mock properties/methods that access database
-    mock_program.admins.all.return_value = [MagicMock(), MagicMock()]  # Return two mock mentors
-    mock_program.admins = mock_program.admins.all
+    # Mock the admins method to return two mock mentors
+    mock_program.admins.return_value = [MagicMock(), MagicMock()]

This makes it clear that admins() is a callable method that returns a list.


28-28: Consider adding timezone info to datetime objects.

For better test reliability and to avoid potential timezone-related issues, consider using timezone-aware datetime objects. While acceptable for test fixtures, using naive datetimes can mask timezone handling bugs.

🔎 Suggested approach
from datetime import datetime, timezone

# For fixed dates:
datetime(2026, 6, 30, tzinfo=timezone.utc)

# For current time:
datetime.now(tz=timezone.utc)

Also applies to: 31-31, 50-50, 55-55, 90-90, 92-92, 115-115, 117-117

backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_module.py (1)

288-289: Split multiple statements to separate lines.

Using semicolons to place multiple statements on one line reduces readability.

🔎 Proposed fix
-        mock_interest1 = MagicMock(); mock_interest1.user = MagicMock(login="user1")
-        mock_interest2 = MagicMock(); mock_interest2.user = MagicMock(login="user2")
+        mock_interest1 = MagicMock()
+        mock_interest1.user = MagicMock(login="user1")
+        mock_interest2 = MagicMock()
+        mock_interest2.user = MagicMock(login="user2")
backend/tests/apps/mentorship/model/test_program.py (3)

23-24: Use timezone-aware datetimes in Django tests.

Multiple datetime() calls lack a tzinfo argument. Django best practices recommend using timezone-aware datetimes to avoid subtle bugs in production. Use django.utils.timezone or pass an explicit timezone.

🔎 Proposed fix
from django.utils import timezone
from datetime import datetime

# Option 1: Use timezone.make_aware
started_at = timezone.make_aware(datetime(2023, 1, 1, 10, 0, 0))

# Option 2: Use timezone.datetime with UTC
from datetime import timezone as dt_timezone
started_at = datetime(2023, 1, 1, 10, 0, 0, tzinfo=dt_timezone.utc)

Also applies to: 36-37, 47-48, 58-59


16-16: Remove unused lambda parameter.

The lambda defining __str__ has an unused self parameter. Either remove it or use a proper method reference.

🔎 Proposed fix
-        self.mentor = MagicMock(spec=Mentor, github_user=self.mentor_github_user, __str__=lambda self: "adminuser")
+        self.mentor = MagicMock(spec=Mentor, github_user=self.mentor_github_user, __str__=lambda: "adminuser")

74-74: Add trailing newline.

The file is missing a newline at the end, which is a PEP 8 convention.

backend/tests/apps/mentorship/model/test_module.py (4)

23-24: Use timezone-aware datetimes in Django tests.

Multiple datetime() calls throughout this test file lack tzinfo arguments. For Django applications, timezone-aware datetimes prevent subtle bugs and align with Django's timezone support.

🔎 Recommended approach
from django.utils import timezone
from datetime import datetime

# Use timezone.make_aware for existing datetime instances
started_at = timezone.make_aware(datetime(2023, 1, 1, 10, 0, 0))

# Or use timezone.now() for current time
created_at = timezone.now()

Also applies to: 48-49, 64-65, 78-79, 116-117, 141-142, 144-145


92-95: Fix docstring formatting.

Both docstrings violate PEP 257 conventions: they should start on the first line (not after opening quotes) and have a blank line between the summary and description.

🔎 Proposed fixes
     def test_unique_module_key_in_program_constraint_mocked(self, mock_create_module):
-        """
-        Pure-mock simulation of unique constraint: first create succeeds,
+        """Pure-mock simulation of unique constraint: first create succeeds,
+        
         second create raises an IntegrityError (mocked).
         """
     def test_module_save(self, mock_create_module):
-        """
-        We cannot call the real Model.save without DB/app registry here,
+        """We cannot call the real Model.save without DB/app registry here,
+        
         so simulate the save behavior by attaching a side_effect to a mock instance's save method.
         This verifies the *expected* effects (key slugification and date inheritance) in a pure-mock way.
         """

Also applies to: 107-111


127-132: Simplify the date inheritance logic.

Lines 129-132 contain redundant logic. The condition checks if not mock_module.started_at, so the subsequent or expressions are unnecessary since mock_module.started_at is guaranteed to be falsy.

🔎 Proposed fix
         def simulate_save_for_inheritance(*args, **kwargs):
             mock_module.key = "date-module"  
             if not mock_module.started_at:
-                mock_module.started_at = mock_module.started_at or mock_module.program.started_at
+                mock_module.started_at = mock_module.program.started_at
             if not mock_module.ended_at:
-                mock_module.ended_at = mock_module.ended_at or mock_module.program.ended_at
+                mock_module.ended_at = mock_module.program.ended_at

Also, the unused *args, **kwargs parameters can be removed:

-        def simulate_save_for_inheritance(*args, **kwargs):
+        def simulate_save_for_inheritance():

156-156: Add trailing newline.

The file is missing a newline at the end, which is a PEP 8 convention.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13de343 and c68231e.

📒 Files selected for processing (24)
  • backend/apps/github/models/__init__.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_enum.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_module.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py
  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py
  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_module.py
  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_program.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
  • backend/tests/apps/mentorship/model/test_experience_level.py
  • backend/tests/apps/mentorship/model/test_issue_user_interest.py
  • backend/tests/apps/mentorship/model/test_mentee.py
  • backend/tests/apps/mentorship/model/test_mentee_module.py
  • backend/tests/apps/mentorship/model/test_mentee_program.py
  • backend/tests/apps/mentorship/model/test_mentor.py
  • backend/tests/apps/mentorship/model/test_mentor_module.py
  • backend/tests/apps/mentorship/model/test_module.py
  • backend/tests/apps/mentorship/model/test_program.py
  • backend/tests/apps/mentorship/model/test_start_end_range.py
  • backend/tests/apps/mentorship/model/test_task.py
  • backend/tests/apps/mentorship/model/test_task_level.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/apps/github/models/init.py
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/model/test_experience_level.py
  • backend/tests/apps/mentorship/model/test_start_end_range.py
  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_module.py
  • backend/tests/apps/mentorship/model/test_module.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py
  • backend/tests/apps/mentorship/model/test_mentor_module.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_enum.py
  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py
  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_program.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_module.py
  • backend/tests/apps/mentorship/model/test_issue_user_interest.py
  • backend/tests/apps/mentorship/model/test_mentee.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
  • backend/tests/apps/mentorship/model/test_mentee_module.py
  • backend/tests/apps/mentorship/model/test_mentee_program.py
  • backend/tests/apps/mentorship/model/test_mentor.py
  • backend/tests/apps/mentorship/model/test_program.py
  • backend/tests/apps/mentorship/model/test_task.py
  • backend/tests/apps/mentorship/model/test_task_level.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/model/test_experience_level.py
  • backend/tests/apps/mentorship/model/test_start_end_range.py
  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_module.py
  • backend/tests/apps/mentorship/model/test_module.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py
  • backend/tests/apps/mentorship/model/test_mentor_module.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_enum.py
  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py
  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_program.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_module.py
  • backend/tests/apps/mentorship/model/test_issue_user_interest.py
  • backend/tests/apps/mentorship/model/test_mentee.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
  • backend/tests/apps/mentorship/model/test_mentee_module.py
  • backend/tests/apps/mentorship/model/test_mentee_program.py
  • backend/tests/apps/mentorship/model/test_mentor.py
  • backend/tests/apps/mentorship/model/test_program.py
  • backend/tests/apps/mentorship/model/test_task.py
  • backend/tests/apps/mentorship/model/test_task_level.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
📚 Learning: 2025-07-16T13:49:58.648Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/module.py:28-29
Timestamp: 2025-07-16T13:49:58.648Z
Learning: In the OWASP Nest mentorship system, mentors can be created with only github_user initially (without nest_user) when assigned to modules. This allows mentors to be assigned before they've signed into the system. When these users eventually sign in, the nest_user is automatically linked to the existing github_user, creating a complete mentor profile. This design provides flexibility in mentor assignment workflows.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py
📚 Learning: 2025-07-13T05:55:46.436Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/program.py:166-166
Timestamp: 2025-07-13T05:55:46.436Z
Learning: In the OWASP Nest mentorship GraphQL mutations, Strawberry GraphQL automatically converts between Django Program instances and ProgramNode types, so mutations can return Program instances directly without manual conversion even when typed as ProgramNode, similar to the Module/ModuleNode pattern.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py
📚 Learning: 2025-08-31T13:47:15.861Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2155
File: frontend/src/server/queries/programsQueries.ts:81-81
Timestamp: 2025-08-31T13:47:15.861Z
Learning: In frontend/src/server/queries/programsQueries.ts, GET_PROGRAM_DETAILS is actively used in frontend/src/app/my/mentorship/programs/[programKey]/edit/page.tsx for program editing functionality and cannot be removed. It serves a different purpose than GET_PROGRAM_ADMIN_DETAILS, providing comprehensive program information needed for editing.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_program.py
📚 Learning: 2025-08-31T13:47:15.861Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2155
File: frontend/src/server/queries/programsQueries.ts:81-81
Timestamp: 2025-08-31T13:47:15.861Z
Learning: In frontend/src/server/queries/programsQueries.ts, GET_PROGRAM_DETAILS and GET_PROGRAM_ADMIN_DETAILS are two separate queries serving different purposes: GET_PROGRAM_DETAILS fetches comprehensive program information while GET_PROGRAM_ADMIN_DETAILS fetches only admin-related details. These queries cannot be removed or merged as they serve different use cases in the application.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_program.py
📚 Learning: 2025-08-31T13:47:15.861Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2155
File: frontend/src/server/queries/programsQueries.ts:81-81
Timestamp: 2025-08-31T13:47:15.861Z
Learning: In frontend/src/server/queries/programsQueries.ts, GET_PROGRAM_DETAILS and GET_PROGRAM_ADMIN_DETAILS are two separate queries serving different purposes: GET_PROGRAM_DETAILS fetches comprehensive program information while GET_PROGRAM_ADMIN_DETAILS fetches only admin-related details.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_program.py
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 2000
File: backend/apps/mentorship/management/commands/sync_module_issues.py:109-145
Timestamp: 2025-08-17T11:55:55.990Z
Learning: In the OWASP/Nest mentorship system, tasks are designed to be assigned to only one assignee per issue, even if GitHub issues can have multiple assignees. The sync_module_issues command correctly uses issue.assignees.first() to create one task per issue for the first assignee only.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
🧬 Code graph analysis (23)
backend/tests/apps/mentorship/model/test_experience_level.py (1)
backend/apps/mentorship/models/common/experience_level.py (2)
  • ExperienceLevel (6-25)
  • ExperienceLevelChoices (12-18)
backend/tests/apps/mentorship/model/test_start_end_range.py (1)
backend/apps/mentorship/models/common/start_end_range.py (1)
  • StartEndRange (6-13)
backend/tests/apps/mentorship/api/internal/queries/test_api_queries_module.py (1)
backend/apps/mentorship/api/internal/queries/module.py (3)
  • get_program_modules (19-26)
  • get_project_modules (29-36)
  • get_module (39-50)
backend/tests/apps/mentorship/model/test_module.py (2)
backend/apps/mentorship/models/managers/module.py (1)
  • PublishedModuleManager (8-13)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py (2)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py (1)
  • command (22-30)
backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py (1)
  • command (38-45)
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py (1)
backend/apps/mentorship/api/internal/nodes/mentor.py (3)
  • avatar_url (13-15)
  • name (18-20)
  • login (23-25)
backend/tests/apps/mentorship/model/test_mentor_module.py (2)
backend/apps/mentorship/models/mentor_module.py (1)
  • MentorModule (11-42)
backend/apps/mentorship/models/mentor.py (1)
  • Mentor (11-40)
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_enum.py (3)
backend/apps/mentorship/api/internal/nodes/enum.py (2)
  • ExperienceLevelEnum (12-18)
  • ProgramStatusEnum (22-27)
backend/apps/mentorship/models/common/experience_level.py (2)
  • ExperienceLevel (6-25)
  • ExperienceLevelChoices (12-18)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py (1)
backend/apps/mentorship/api/internal/queries/mentorship.py (3)
  • is_mentor (36-48)
  • get_mentee_details (51-82)
  • get_mentee_module_issues (85-124)
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py (2)
backend/apps/mentorship/api/internal/nodes/enum.py (1)
  • ExperienceLevelEnum (12-18)
backend/apps/mentorship/api/internal/nodes/mentee.py (2)
  • resolve_avatar_url (23-25)
  • resolve_experience_level (28-30)
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py (3)
backend/apps/mentorship/api/internal/nodes/enum.py (2)
  • ExperienceLevelEnum (12-18)
  • ProgramStatusEnum (22-27)
backend/apps/mentorship/api/internal/nodes/mentor.py (1)
  • name (18-20)
backend/apps/mentorship/api/internal/nodes/program.py (1)
  • admins (32-34)
backend/tests/apps/mentorship/api/internal/queries/test_api_queries_program.py (4)
backend/apps/mentorship/api/internal/queries/program.py (2)
  • get_program (23-32)
  • my_programs (35-80)
backend/apps/mentorship/api/internal/nodes/mentor.py (1)
  • login (23-25)
backend/apps/owasp/api/internal/nodes/member_snapshot.py (1)
  • github_user (32-34)
backend/apps/mentorship/api/internal/nodes/program.py (1)
  • admins (32-34)
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_module.py (4)
backend/apps/mentorship/api/internal/nodes/enum.py (1)
  • ExperienceLevelEnum (12-18)
backend/apps/github/api/internal/nodes/issue.py (1)
  • labels (50-52)
backend/apps/mentorship/models/task.py (1)
  • Task (10-75)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-36)
backend/tests/apps/mentorship/model/test_issue_user_interest.py (1)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-36)
backend/tests/apps/mentorship/model/test_mentee.py (2)
backend/apps/mentorship/models/mentee.py (1)
  • Mentee (11-49)
backend/tests/apps/mentorship/model/test_mentor.py (1)
  • test_str_returns_github_login (8-14)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py (2)
backend/apps/mentorship/models/task.py (1)
  • Task (10-75)
backend/apps/mentorship/management/commands/mentorship_sync_module_issues.py (5)
  • _extract_repo_full_name (28-40)
  • _get_status (42-50)
  • _get_last_assigned_date (52-74)
  • _build_repo_label_to_issue_map (76-92)
  • _process_module (94-203)
backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py (1)
backend/apps/mentorship/management/commands/mentorship_update_comments.py (3)
  • process_mentorship_modules (31-55)
  • process_module (57-89)
  • process_issue_interests (91-160)
backend/tests/apps/mentorship/model/test_mentee_module.py (2)
backend/apps/mentorship/models/mentee.py (1)
  • Mentee (11-49)
backend/apps/mentorship/models/mentee_module.py (1)
  • MenteeModule (11-45)
backend/tests/apps/mentorship/model/test_mentee_program.py (3)
backend/apps/mentorship/models/mentee.py (1)
  • Mentee (11-49)
backend/apps/mentorship/models/mentee_program.py (1)
  • MenteeProgram (11-39)
backend/tests/apps/mentorship/model/test_mentee_module.py (2)
  • MockIntegrityError (9-10)
  • test_unique_together_constraint (53-71)
backend/tests/apps/mentorship/model/test_mentor.py (3)
backend/apps/mentorship/models/mentor.py (1)
  • Mentor (11-40)
backend/tests/apps/mentorship/model/test_mentee.py (1)
  • test_str_returns_github_login (8-14)
backend/apps/mentorship/api/internal/nodes/mentor.py (1)
  • login (23-25)
backend/tests/apps/mentorship/model/test_program.py (3)
backend/apps/mentorship/models/mentor.py (1)
  • Mentor (11-40)
backend/apps/mentorship/api/internal/nodes/mentor.py (2)
  • login (23-25)
  • name (18-20)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/tests/apps/mentorship/model/test_task.py (1)
backend/apps/mentorship/models/task.py (1)
  • Task (10-75)
backend/tests/apps/mentorship/model/test_task_level.py (1)
backend/apps/mentorship/models/task_level.py (1)
  • TaskLevel (8-61)
🪛 Ruff (0.14.10)
backend/tests/apps/mentorship/model/test_experience_level.py

1-1: File backend/tests/apps/mentorship/model/test_experience_level.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/model/test_start_end_range.py

1-1: File backend/tests/apps/mentorship/model/test_start_end_range.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


9-9: datetime.datetime() called without a tzinfo argument

(DTZ001)


10-10: datetime.datetime() called without a tzinfo argument

(DTZ001)

backend/tests/apps/mentorship/api/internal/queries/test_api_queries_module.py

1-1: File backend/tests/apps/mentorship/api/internal/queries/test_api_queries_module.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


87-87: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)

backend/tests/apps/mentorship/model/test_module.py

1-1: File backend/tests/apps/mentorship/model/test_module.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


23-23: datetime.datetime() called without a tzinfo argument

(DTZ001)


24-24: datetime.datetime() called without a tzinfo argument

(DTZ001)


48-48: datetime.datetime() called without a tzinfo argument

(DTZ001)


49-49: datetime.datetime() called without a tzinfo argument

(DTZ001)


64-64: datetime.datetime() called without a tzinfo argument

(DTZ001)


65-65: datetime.datetime() called without a tzinfo argument

(DTZ001)


78-78: datetime.datetime() called without a tzinfo argument

(DTZ001)


79-79: datetime.datetime() called without a tzinfo argument

(DTZ001)


92-95: 1 blank line required between summary line and description

(D205)


92-95: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


107-111: 1 blank line required between summary line and description

(D205)


107-111: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


116-116: datetime.datetime() called without a tzinfo argument

(DTZ001)


117-117: datetime.datetime() called without a tzinfo argument

(DTZ001)


127-127: Unused function argument: args

(ARG001)


127-127: Unused function argument: kwargs

(ARG001)


141-141: datetime.datetime() called without a tzinfo argument

(DTZ001)


142-142: datetime.datetime() called without a tzinfo argument

(DTZ001)


144-144: datetime.datetime() called without a tzinfo argument

(DTZ001)


145-145: datetime.datetime() called without a tzinfo argument

(DTZ001)


156-156: No newline at end of file

Add trailing newline

(W292)

backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py

1-1: File backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


17-17: Boolean default positional argument in function definition

(FBT002)


18-18: First line of docstring should be in imperative mood: "Helper: return a queryset-like MagicMock that is iterable and supports .exists()."

(D401)


27-27: Argument name MockTaskLevel should be lowercase

(N803)


28-28: No blank lines allowed after function docstring (found 1)

Remove blank line(s) after function docstring

(D202)


39-39: Argument name MockTaskLevel should be lowercase

(N803)


39-39: Argument name MockIssue should be lowercase

(N803)


40-40: No blank lines allowed after function docstring (found 1)

Remove blank line(s) after function docstring

(D202)


52-52: Multiple statements on one line (semicolon)

(E702)


53-53: Multiple statements on one line (semicolon)

(E702)


54-54: Multiple statements on one line (semicolon)

(E702)


97-97: Argument name MockTaskLevel should be lowercase

(N803)


97-97: Argument name MockIssue should be lowercase

(N803)


106-106: Multiple statements on one line (semicolon)

(E702)

backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py

1-1: File backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


68-68: No newline at end of file

Add trailing newline

(W292)

backend/tests/apps/mentorship/model/test_mentor_module.py

1-1: File backend/tests/apps/mentorship/model/test_mentor_module.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_enum.py

1-1: File backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_enum.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py

1-1: File backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


348-348: Local variable mock_issue1 is assigned to but never used

Remove assignment to unused variable mock_issue1

(F841)


350-350: Local variable mock_issue3 is assigned to but never used

Remove assignment to unused variable mock_issue3

(F841)

backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py

1-1: File backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


24-24: Unnecessary assignment to mentee_node before return statement

Remove unnecessary assignment

(RET504)


57-57: Use specific rule codes when ignoring type issues

(PGH003)


61-61: No newline at end of file

Add trailing newline

(W292)

backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py

1-1: File backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


28-28: datetime.datetime() called without a tzinfo argument

(DTZ001)


31-31: datetime.datetime() called without a tzinfo argument

(DTZ001)


50-50: datetime.datetime() called without a tzinfo argument

(DTZ001)


55-55: datetime.datetime() called without a tzinfo argument

(DTZ001)


79-79: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


80-80: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


83-83: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


90-90: datetime.datetime.now() called without a tz argument

(DTZ005)


92-92: datetime.datetime.now() called without a tz argument

(DTZ005)


100-100: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


101-101: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


102-102: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


106-106: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


115-115: datetime.datetime.now() called without a tz argument

(DTZ005)


117-117: datetime.datetime.now() called without a tz argument

(DTZ005)


127-127: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


128-128: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


129-129: No newline at end of file

Add trailing newline

(W292)

backend/tests/apps/mentorship/api/internal/queries/test_api_queries_program.py

1-1: File backend/tests/apps/mentorship/api/internal/queries/test_api_queries_program.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


36-36: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)

backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_module.py

1-1: File backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_module.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


1-2: 1 blank line required between summary line and description

(D205)


1-2: Multi-line docstring closing quotes should be on a separate line

Move closing quotes to new line

(D209)


15-16: 1 blank line required between summary line and description

(D205)


15-16: Multi-line docstring closing quotes should be on a separate line

Move closing quotes to new line

(D209)


25-25: datetime.datetime() called without a tzinfo argument

(DTZ001)


32-32: datetime.datetime() called without a tzinfo argument

(DTZ001)


184-184: datetime.datetime() called without a tzinfo argument

(DTZ001)


190-190: datetime.datetime() called without a tzinfo argument

(DTZ001)


288-288: Multiple statements on one line (semicolon)

(E702)


289-289: Multiple statements on one line (semicolon)

(E702)


313-313: datetime.datetime() called without a tzinfo argument

(DTZ001)


316-316: datetime.datetime() called without a tzinfo argument

(DTZ001)


332-332: datetime.datetime() called without a tzinfo argument

(DTZ001)


335-335: datetime.datetime() called without a tzinfo argument

(DTZ001)


352-352: datetime.datetime.now() called without a tz argument

(DTZ005)


354-354: datetime.datetime.now() called without a tz argument

(DTZ005)


362-362: datetime.datetime.now() called without a tz argument

(DTZ005)


363-363: datetime.datetime.now() called without a tz argument

(DTZ005)

backend/tests/apps/mentorship/model/test_issue_user_interest.py

1-1: File backend/tests/apps/mentorship/model/test_issue_user_interest.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/model/test_mentee.py

1-1: File backend/tests/apps/mentorship/model/test_mentee.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py

1-1: File backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


12-12: Boolean default positional argument in function definition

(FBT002)


40-40: Wrong type passed to first argument of pytest.mark.parametrize; expected tuple

Use a tuple for the first argument

(PT006)


44-44: Duplicate of test case at index 0 in pytest.mark.parametrize

Remove duplicate test case

(PT014)


56-56: Multiple statements on one line (semicolon)

(E702)


69-69: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


70-70: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


71-71: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


72-72: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


76-76: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


97-97: Variable MockIssue in function should be lowercase

(N806)


115-115: Argument name MockIssue should be lowercase

(N803)


115-115: Argument name MockTask should be lowercase

(N803)


116-116: Multiple statements on one line (semicolon)

(E702)


116-116: Multiple statements on one line (semicolon)

(E702)


121-121: Multiple statements on one line (semicolon)

(E702)


121-121: Multiple statements on one line (semicolon)

(E702)


128-128: Multiple statements on one line (semicolon)

(E702)


152-152: Local variable num_linked is assigned to but never used

Remove assignment to unused variable num_linked

(F841)


185-185: Unused function argument: command

(ARG001)


185-185: Test function parameter mock_module has default argument

Remove default argument

(PT028)


185-185: Unused function argument: mock_repository

(ARG001)


185-185: Test function parameter mock_repository has default argument

Remove default argument

(PT028)


186-186: Multiple statements on one line (semicolon)

(E702)


186-186: Multiple statements on one line (semicolon)

(E702)


193-193: Variable MockTask in function should be lowercase

(N806)

backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py

1-1: File backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


10-10: Boolean default positional argument in function definition

(FBT002)


68-68: Argument name MockModule should be lowercase

(N803)


76-76: Argument name MockModule should be lowercase

(N803)


88-88: Argument name MockIssue should be lowercase

(N803)


109-109: Argument name MockIssueUserInterest should be lowercase

(N803)


135-135: Argument name MockIssueUserInterest should be lowercase

(N803)


161-161: Argument name MockIssueUserInterest should be lowercase

(N803)


189-189: Argument name MockIssueUserInterest should be lowercase

(N803)


215-215: Argument name MockIssueUserInterest should be lowercase

(N803)

backend/tests/apps/mentorship/model/test_mentee_module.py

1-1: File backend/tests/apps/mentorship/model/test_mentee_module.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


16-16: Unused lambda argument: self

(ARG005)


18-18: Unused lambda argument: self

(ARG005)


22-22: datetime.datetime() called without a tzinfo argument

(DTZ001)


23-23: datetime.datetime() called without a tzinfo argument

(DTZ001)


62-62: datetime.datetime() called without a tzinfo argument

(DTZ001)


68-68: datetime.datetime() called without a tzinfo argument

(DTZ001)

backend/tests/apps/mentorship/model/test_mentee_program.py

1-1: File backend/tests/apps/mentorship/model/test_mentee_program.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


19-19: Unused lambda argument: self

(ARG005)


25-25: Unused lambda argument: self

(ARG005)


30-30: datetime.datetime() called without a tzinfo argument

(DTZ001)


31-31: datetime.datetime() called without a tzinfo argument

(DTZ001)


75-75: datetime.datetime() called without a tzinfo argument

(DTZ001)


82-82: datetime.datetime() called without a tzinfo argument

(DTZ001)

backend/tests/apps/mentorship/model/test_mentor.py

1-1: File backend/tests/apps/mentorship/model/test_mentor.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/model/test_program.py

1-1: File backend/tests/apps/mentorship/model/test_program.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


9-9: Module level import not at top of file

(E402)


10-10: Module level import not at top of file

(E402)


16-16: Unused lambda argument: self

(ARG005)


23-23: datetime.datetime() called without a tzinfo argument

(DTZ001)


24-24: datetime.datetime() called without a tzinfo argument

(DTZ001)


36-36: datetime.datetime() called without a tzinfo argument

(DTZ001)


37-37: datetime.datetime() called without a tzinfo argument

(DTZ001)


47-47: datetime.datetime() called without a tzinfo argument

(DTZ001)


48-48: datetime.datetime() called without a tzinfo argument

(DTZ001)


58-58: datetime.datetime() called without a tzinfo argument

(DTZ001)


59-59: datetime.datetime() called without a tzinfo argument

(DTZ001)


74-74: No newline at end of file

Add trailing newline

(W292)

backend/tests/apps/mentorship/model/test_task.py

1-1: File backend/tests/apps/mentorship/model/test_task.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/model/test_task_level.py

1-1: File backend/tests/apps/mentorship/model/test_task_level.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

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: 7

🤖 Fix all issues with AI Agents
In
@backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py:
- Around line 44-46: The method resolve_experience_level currently returns
ExperienceLevelEnum when experience_level is set but a string when None; make it
consistently return a string by changing resolve_experience_level to return
self.experience_level.value if self.experience_level else "beginner" and keep
the return type annotation as str, and update the test
(test_mentee_node_resolve_experience_level) to expect the string "beginner"
instead of ExperienceLevelEnum.BEGINNER.

In
@backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py:
- Around line 37-38: The test wrongly assigns mock_program.admins to the all
method itself; instead set admins to a mock relationship manager whose all()
returns the list of mentors. Replace the current lines with something like:
create a MagicMock() for mock_program.admins (e.g., mock_program.admins =
MagicMock()) and then set mock_program.admins.all.return_value = [MagicMock(),
MagicMock()] so that calls to mock_program.admins.all() behave like the real
relationship manager.

In @backend/tests/apps/mentorship/model/test_module.py:
- Around line 28-33: Replace the mock-based test with a Django TestCase that
creates a real Module instance and asserts its __str__ output: in
test_str_returns_name use Module.objects.create(...) to create a Module
(providing required relations like program and project or creating those
fixtures), then assert str(module) == "Security Basics" to validate the actual
Module.__str__ implementation.
- Around line 23-24: Replace naive datetimes used for started_at and ended_at
with timezone-aware datetimes: import django.utils.timezone and construct
datetimes with timezone.datetime(..., tzinfo=timezone.utc) or use
timezone.make_aware/timezone.now where appropriate; update every occurrence
referenced (started_at, ended_at and the other instances at the lines noted) so
tests use timezone-aware values and ensure the import for timezone is added to
the test module.
- Around line 36-87: The test currently patches Module.objects.create and
simulates Module.save via a MagicMock, so it doesn't validate the real
Module.save behavior; replace the mock-based test with real ORM-backed tests
using Django TestCase: remove the patching of Module.objects.create and
MagicMock usage, create a Program via Program.objects.create (with
timezone-aware started_at/ended_at), then create a Module via
Module.objects.create or instantiate and call module.save() to exercise the
actual Module.save method, and assert that module.key is slugified and
started_at/ended_at are inherited when None and preserved when explicitly
provided; update test names (e.g., test_module_save_inherits_dates_from_program
and test_module_save_respects_explicit_dates) to reflect the two behaviors being
verified.

In @backend/tests/apps/mentorship/model/test_program.py:
- Around line 13-22: Tests in TestProgram currently only assert enum values and
Program.__str__; convert them to real Django DB tests by subclassing
django.test.TestCase (replace the lightweight class TestProgram), create Program
instances via Program.objects.create in methods like
test_create_program_with_required_fields and
test_create_program_with_all_fields, assert created instance fields (id, name),
defaults and auto-generated values such as key (Program.key), timestamps
(started_at/ended_at), list fields (experience_levels, domains, tags) and
mentees_limit, and verify str(program) returns the name; also add tests for
relationships (create Mentor/Mentee linked to Program and assert relationship
queries) and ensure the file ends with a newline to fix W292.
- Around line 1-10: Remove unused imports and the unused helper from the test
file: delete the unused imports datetime, MagicMock, and patch, remove the
unused class MockIntegrityError, and drop unused model imports Mentor and
GithubUser if they are not referenced elsewhere in this test file; keep only the
necessary import(s) for Program (apps.mentorship.models.Program) and reorganize
the remaining imports into a clean, minimal import block.
🧹 Nitpick comments (10)
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py (1)

79-86: Consider using is for type comparisons.

The tests check type annotations using ==, which works but is not idiomatic Python. The style guide recommends using is for type identity comparisons.

🔎 Example refactor for one assertion
-    assert CreateProgramInput.__annotations__["name"] == str
+    assert CreateProgramInput.__annotations__["name"] is str

Apply this pattern to all type annotation assertions throughout the three test functions.

Also applies to: 100-109, 127-129

backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py (6)

3-4: Consolidate datetime imports.

The file imports both from datetime import datetime and import datetime as dt, which creates redundancy. Later in the file, dt.timezone.utc is used. Consider consolidating to one approach:

Option 1 (recommended): Import both datetime and timezone directly:

-from datetime import datetime
-import datetime as dt
+from datetime import datetime, timezone

Then update usages of dt.timezone.utc to just timezone.utc (lines 68-71, 75).

Option 2: Use the module import consistently:

-from datetime import datetime
 import datetime as dt

Then update bare datetime(...) calls to dt.datetime(...).


39-49: Use tuple for parametrize argument names.

Line 40 should use a tuple for the parameter names to follow pytest conventions.

🔎 Proposed fix
 @pytest.mark.parametrize(
-    "repo_url, expected",
+    ("repo_url", "expected"),
     [
         ("https://github.com/owner/repo", "owner/repo"),
         ("https://www.github.com/owner/repo/sub/path", "owner/repo"),

54-60: Consider splitting statements for readability.

Line 55 places multiple statements on one line using semicolons, which reduces readability.

🔎 Proposed fix
 def test_get_status_variants(command):
-    issue = MagicMock(); issue.state = "closed"
+    issue = MagicMock()
+    issue.state = "closed"
     assert command._get_status(issue, MagicMock()) == Task.Status.COMPLETED

63-85: Consider using datetime.UTC for timezone specification.

Lines 68-71 and 75 use dt.timezone.utc. Python 3.11+ introduced datetime.UTC as a cleaner alias. If your project targets Python 3.11+, consider updating:

-    e1 = MagicMock(event="commented", created_at=datetime(2023, 1, 1, tzinfo=dt.timezone.utc))
+    e1 = MagicMock(event="commented", created_at=datetime(2023, 1, 1, tzinfo=timezone.UTC))

Apply similarly to lines 69-71 and 75. (This assumes you've consolidated imports as suggested earlier.)

Otherwise, the test logic is excellent—it thoroughly covers the found, not-found, and exception scenarios.


112-182: Excellent test coverage with comprehensive assertions.

This test thoroughly validates the _process_module function:

  • Issue linking via module.issues.set
  • Task creation with correct status mapping (IN_PROGRESS for open+assigned, COMPLETED for closed)
  • Return value verification (num_linked)
  • Console output validation

Great job addressing the previous review comment by adding the assert num_linked == 3 assertion on line 158.

Optional style improvements:

  • Lines 115, 120, 127: Consider splitting semicolon-separated statements onto separate lines for better readability
  • Lines 114: Mock parameter naming (MockIssue, MockTask) could be lowercased per PEP 8, though PascalCase is common in test patches

185-208: Consider using the command fixture for consistency.

The test instantiates a new Command() instance directly (line 198) instead of using the command fixture like other tests. This means stdout/stderr aren't mocked, making the test inconsistent with the rest of the file.

🔎 Proposed fix
-def test_process_module_no_matches():
-    mock_repo = MagicMock(); mock_repo.id = 7; mock_repo.name = "r"
+def test_process_module_no_matches(command):
+    mock_repo = MagicMock()
+    mock_repo.id = 7
+    mock_repo.name = "r"
     mock_module = MagicMock()
     mock_module.project.repositories.all.return_value = [mock_repo]
     mock_module.labels = ["some-label"]
 
     repo_label_to_issue_ids = {}
 
     with patch("apps.mentorship.management.commands.mentorship_sync_module_issues.Task") as MockTask, \
          patch("apps.mentorship.management.commands.mentorship_sync_module_issues.transaction.atomic") as mock_atomic:
         mock_atomic.return_value.__enter__.return_value = None
         mock_atomic.return_value.__exit__.return_value = None
 
-        num_linked = Command()._process_module(
+        num_linked = command._process_module(
             module=mock_module,
             repo_label_to_issue_ids=repo_label_to_issue_ids,
             gh_client=MagicMock(),
             repo_cache={},
             verbosity=1,
         )

This also splits the semicolon-separated statements on line 186 for better readability.

backend/tests/apps/mentorship/model/test_module.py (1)

38-42: Address minor formatting issues.

Static analysis flags several style issues:

  1. Docstring formatting (lines 38-42): Multi-line docstring should start at the first line and have a blank line between summary and description.
  2. Unused arguments (line 58): The args and kwargs parameters in simulate_save_for_inheritance are unused.
  3. Missing newline (line 87): Add a trailing newline at the end of the file.
🔎 Formatting fixes
-    def test_module_save(self, mock_create_module):
-        """
-        We cannot call the real Model.save without DB/app registry here,
-        so simulate the save behavior by attaching a side_effect to a mock instance's save method.
-        This verifies the *expected* effects (key slugification and date inheritance) in a pure-mock way.
-        """
+    def test_module_save(self, mock_create_module):
+        """We cannot call the real Model.save without DB/app registry here.
+        
+        Simulates the save behavior by attaching a side_effect to a mock instance's save method.
+        This verifies the *expected* effects (key slugification and date inheritance) in a pure-mock way.
+        """

-        def simulate_save_for_inheritance(*args, **kwargs):
+        def simulate_save_for_inheritance():

And add a newline at the end of the file (line 87).

Also applies to: 58-58, 87-87

backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py (1)

23-24: Optional: Return directly without assignment.

The assignment before return is unnecessary. Consider returning the MenteeNode constructor directly.

🔎 Proposed simplification
 @pytest.fixture
 def mock_mentee_node():
     """Fixture for a mock MenteeNode instance."""
-    mentee_node = MenteeNode(
+    return MenteeNode(
         id="1",
         login="testmentee",
         name="Test Mentee",
         avatar_url="https://example.com/avatar.jpg",
         bio="A test mentee",
         experience_level=ExperienceLevelEnum.BEGINNER,
         domains=["python"],
         tags=["backend"],
     )
-    return mentee_node
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py (1)

20-33: Optional: Return directly without intermediate assignment.

Both mock_mentor_node and mock_mentor_node_no_github_user fixtures assign to a variable before returning. Consider streamlining by removing the intermediate assignment.

🔎 Proposed simplification
 @pytest.fixture
 def mock_mentor_node(mock_github_user):
     """Fixture for a mock MentorNode instance."""
     mentor_node = MentorNode(id="1")
     mentor_node.github_user = mock_github_user
     return mentor_node


 @pytest.fixture
 def mock_mentor_node_no_github_user():
     """Fixture for a mock MentorNode instance without a GitHub user."""
     mentor_node = MentorNode(id="2")
     mentor_node.github_user = None
     return mentor_node

While this pattern is acceptable, if you prefer more concise fixtures, you could inline the attribute assignment. However, the current approach is clear and explicit, so this is truly optional.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c68231e and cbe391f.

📒 Files selected for processing (6)
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/model/test_module.py
  • backend/tests/apps/mentorship/model/test_program.py
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-07-16T13:49:58.648Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/module.py:28-29
Timestamp: 2025-07-16T13:49:58.648Z
Learning: In the OWASP Nest mentorship system, mentors can be created with only github_user initially (without nest_user) when assigned to modules. This allows mentors to be assigned before they've signed into the system. When these users eventually sign in, the nest_user is automatically linked to the existing github_user, creating a complete mentor profile. This design provides flexibility in mentor assignment workflows.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py
  • backend/tests/apps/mentorship/model/test_module.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/model/test_program.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py
  • backend/tests/apps/mentorship/model/test_module.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/model/test_program.py
📚 Learning: 2025-07-13T05:55:46.436Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/program.py:166-166
Timestamp: 2025-07-13T05:55:46.436Z
Learning: In the OWASP Nest mentorship GraphQL mutations, Strawberry GraphQL automatically converts between Django Program instances and ProgramNode types, so mutations can return Program instances directly without manual conversion even when typed as ProgramNode, similar to the Module/ModuleNode pattern.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py
📚 Learning: 2025-09-29T06:02:35.566Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2178
File: frontend/src/components/SingleModuleCard.tsx:54-54
Timestamp: 2025-09-29T06:02:35.566Z
Learning: In the Module type from types/mentorship.ts, the experienceLevel field is required (experienceLevel: ExperienceLevelEnum), not optional, so null/undefined checks are not needed when accessing this field.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 2000
File: backend/apps/mentorship/management/commands/sync_module_issues.py:109-145
Timestamp: 2025-08-17T11:55:55.990Z
Learning: In the OWASP/Nest mentorship system, tasks are designed to be assigned to only one assignee per issue, even if GitHub issues can have multiple assignees. The sync_module_issues command correctly uses issue.assignees.first() to create one task per issue for the first assignee only.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
🧬 Code graph analysis (5)
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py (1)
backend/apps/mentorship/api/internal/nodes/mentor.py (3)
  • avatar_url (13-15)
  • name (18-20)
  • login (23-25)
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py (3)
backend/apps/mentorship/api/internal/nodes/enum.py (2)
  • ExperienceLevelEnum (12-18)
  • ProgramStatusEnum (22-27)
backend/apps/mentorship/api/internal/nodes/mentor.py (1)
  • name (18-20)
backend/apps/mentorship/api/internal/nodes/program.py (1)
  • admins (32-34)
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py (2)
backend/apps/mentorship/api/internal/nodes/enum.py (1)
  • ExperienceLevelEnum (12-18)
backend/apps/mentorship/api/internal/nodes/mentee.py (2)
  • resolve_avatar_url (23-25)
  • resolve_experience_level (28-30)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py (2)
backend/apps/mentorship/models/task.py (1)
  • Task (10-75)
backend/apps/mentorship/management/commands/mentorship_sync_module_issues.py (5)
  • _extract_repo_full_name (28-40)
  • _get_status (42-50)
  • _get_last_assigned_date (52-74)
  • _build_repo_label_to_issue_map (76-92)
  • _process_module (94-203)
backend/tests/apps/mentorship/model/test_program.py (2)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/apps/mentorship/api/internal/nodes/mentor.py (1)
  • name (18-20)
🪛 Ruff (0.14.10)
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py

1-1: File backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py is part of an implicit namespace package. Add an __init__.py.

(INP001)

backend/tests/apps/mentorship/model/test_module.py

1-1: File backend/tests/apps/mentorship/model/test_module.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


23-23: datetime.datetime() called without a tzinfo argument

(DTZ001)


24-24: datetime.datetime() called without a tzinfo argument

(DTZ001)


38-42: 1 blank line required between summary line and description

(D205)


38-42: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


47-47: datetime.datetime() called without a tzinfo argument

(DTZ001)


48-48: datetime.datetime() called without a tzinfo argument

(DTZ001)


58-58: Unused function argument: args

(ARG001)


58-58: Unused function argument: kwargs

(ARG001)


72-72: datetime.datetime() called without a tzinfo argument

(DTZ001)


73-73: datetime.datetime() called without a tzinfo argument

(DTZ001)


75-75: datetime.datetime() called without a tzinfo argument

(DTZ001)


76-76: datetime.datetime() called without a tzinfo argument

(DTZ001)


87-87: No newline at end of file

Add trailing newline

(W292)

backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py

1-1: File backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


28-28: datetime.datetime() called without a tzinfo argument

(DTZ001)


31-31: datetime.datetime() called without a tzinfo argument

(DTZ001)


50-50: datetime.datetime() called without a tzinfo argument

(DTZ001)


55-55: datetime.datetime() called without a tzinfo argument

(DTZ001)


79-79: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


80-80: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


83-83: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


90-90: datetime.datetime.now() called without a tz argument

(DTZ005)


92-92: datetime.datetime.now() called without a tz argument

(DTZ005)


100-100: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


101-101: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


102-102: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


106-106: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


115-115: datetime.datetime.now() called without a tz argument

(DTZ005)


117-117: datetime.datetime.now() called without a tz argument

(DTZ005)


127-127: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


128-128: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)

backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py

1-1: File backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentee.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


24-24: Unnecessary assignment to mentee_node before return statement

Remove unnecessary assignment

(RET504)

backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py

1-1: File backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


12-12: Boolean default positional argument in function definition

(FBT002)


40-40: Wrong type passed to first argument of pytest.mark.parametrize; expected tuple

Use a tuple for the first argument

(PT006)


55-55: Multiple statements on one line (semicolon)

(E702)


68-68: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


69-69: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


70-70: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


71-71: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


75-75: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


96-96: Variable MockIssue in function should be lowercase

(N806)


114-114: Argument name MockIssue should be lowercase

(N803)


114-114: Argument name MockTask should be lowercase

(N803)


115-115: Multiple statements on one line (semicolon)

(E702)


115-115: Multiple statements on one line (semicolon)

(E702)


120-120: Multiple statements on one line (semicolon)

(E702)


120-120: Multiple statements on one line (semicolon)

(E702)


127-127: Multiple statements on one line (semicolon)

(E702)


186-186: Multiple statements on one line (semicolon)

(E702)


186-186: Multiple statements on one line (semicolon)

(E702)


193-193: Variable MockTask in function should be lowercase

(N806)

backend/tests/apps/mentorship/model/test_program.py

1-1: File backend/tests/apps/mentorship/model/test_program.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


9-9: Module level import not at top of file

(E402)


10-10: Module level import not at top of file

(E402)


22-22: No newline at end of file

Add trailing newline

(W292)

🔇 Additional comments (7)
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py (2)

43-58: LGTM!

The field assertions correctly verify the mock fixture's attribute assignments.


67-74: LGTM!

The test correctly verifies PaginatedPrograms field assignments.

backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py (4)

12-18: LGTM!

The make_qs helper provides a clean way to mock Django QuerySets for testing. The implementation correctly supports iteration, .exists(), and .all() methods.


21-30: LGTM!

The fixture correctly sets up the Command instance with mocked stdout, stderr, and style for testing. This follows Django testing best practices.


33-36: LGTM!

Clean test for extracting repository full name from an object with a path attribute.


88-109: LGTM!

Excellent test coverage for the repo-label map building logic. The test correctly validates:

  • Label normalization (case-insensitive grouping)
  • Proper aggregation by (repo_id, normalized_label) key
  • Correct issue ID deduplication
  • Console output messages

The mock variable naming (MockIssue) is a minor style convention that can be optionally lowercased if you prefer strict adherence to PEP 8.

backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py (1)

36-68: Excellent test coverage!

The test suite comprehensively covers both the presence and absence of github_user, validating the fallback behavior to empty strings. The test structure is clear and well-organized.

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: 2

🤖 Fix all issues with AI Agents
In @backend/tests/apps/mentorship/model/test_module.py:
- Around line 1-10: Remove the unused imports and class to clean up the test:
delete the imports PublishedModuleManager, Mentor, Issue, and GithubUser from
the top import block and remove the unused MockIntegrityError class definition;
keep only the imports actually referenced (e.g., Module, Program, datetime,
pytest, django.utils.timezone, MagicMock/patch) and run tests to ensure no
remaining references to those symbols.
- Around line 59-64: In simulate_save_for_inheritance, simplify the redundant
conditional assignments: inside the if not mock_module.started_at block set
mock_module.started_at directly from mock_module.program.started_at (drop the
"or" with mock_module.started_at), and likewise inside the if not
mock_module.ended_at block set mock_module.ended_at directly from
mock_module.program.ended_at; update the assignments in the
simulate_save_for_inheritance function to remove the always-falsy left-side of
the "or" expressions.
🧹 Nitpick comments (4)
backend/tests/apps/mentorship/model/test_start_end_range.py (1)

7-18: Test provides minimal coverage.

The test simply assigns datetime values to a mock and asserts them back, which doesn't validate any actual StartEndRange model behavior or logic. Since StartEndRange is an abstract model with field declarations, this mock-based approach exercises very little.

That said, given the maintainer's preference for pure mock tests (based on learnings), this approach is acceptable if the goal is to verify field presence rather than database-level behavior.

backend/tests/apps/mentorship/model/test_program.py (1)

6-15: Add trailing newline to fix linting warning.

The tests correctly validate the ProgramStatus enum values and the __str__ method using lightweight mock-based testing (consistent with maintainer preference).

🔎 Proposed fix
     def test_str_returns_name(self):
         mock_program_instance = Program(name="Security Program")
         assert Program.__str__(mock_program_instance) == "Security Program"
+
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py (1)

60-146: Comprehensive coverage of node and input types.

The tests effectively validate:

  • Field assignments on ProgramNode
  • Resolver behavior (admins)
  • Input type annotations and default values
  • Pagination structure

The use of == for type annotation checks (flagged by E721) is appropriate in this context—you're checking the __annotations__ dictionary, not performing isinstance checks.

Optional: Consider timezone-aware datetimes for Django consistency.

While the naive datetime objects work for these mock tests, using timezone-aware datetimes (django.utils.timezone.datetime(...) with tzinfo=timezone.utc) would better align with Django's default USE_TZ=True setting and avoid potential confusion.

backend/tests/apps/mentorship/model/test_module.py (1)

17-88: Add trailing newline and consider cleaning up test complexity.

The file is missing a trailing newline (W292). Additionally, the simulate_save_for_inheritance function has unused *args, **kwargs parameters that can be removed.

🔎 Proposed fix
-        def simulate_save_for_inheritance(*args, **kwargs):
+        def simulate_save_for_inheritance():
             mock_module.key = "date-module"  
             if not mock_module.started_at:
                 mock_module.started_at = mock_module.program.started_at
             if not mock_module.ended_at:
                 mock_module.ended_at = mock_module.program.ended_at
 
         mock_module.save.side_effect = simulate_save_for_inheritance
         mock_create_module.return_value = mock_module
 
         module = Module.objects.create(name="Auto Date Module", program=program_with_dates, project=self.project)
         module.save()  
 
         assert module.key == "date-module"
         assert module.started_at == django.utils.timezone.datetime(2024, 2, 1, tzinfo=django.utils.timezone.UTC)
         assert module.ended_at == django.utils.timezone.datetime(2024, 2, 28, tzinfo=django.utils.timezone.UTC)
 
         explicit_start = django.utils.timezone.datetime(2024, 3, 1, tzinfo=django.utils.timezone.UTC)
         explicit_end = django.utils.timezone.datetime(2024, 3, 31, tzinfo=django.utils.timezone.UTC)
 
         mock_module.started_at = explicit_start
         mock_module.ended_at = explicit_end
         mock_create_module.return_value = mock_module
 
         module = Module.objects.create(name="Auto Date Module", program=program_with_dates, project=self.project)
         module.save()  
 
         assert module.started_at == explicit_start
         assert module.ended_at == explicit_end
         assert module.key == "date-module"
+

Based on learnings, the maintainer prefers pure mock tests for this test suite, so the mock-based approach is appropriate despite its complexity.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cbe391f and e2113e5.

📒 Files selected for processing (5)
  • backend/apps/mentorship/api/internal/nodes/mentee.py
  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py
  • backend/tests/apps/mentorship/model/test_module.py
  • backend/tests/apps/mentorship/model/test_program.py
  • backend/tests/apps/mentorship/model/test_start_end_range.py
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:03.537Z
Learning: In the mentorship test suite (backend/tests/apps/mentorship/), the maintainer kart-u prefers pure mock tests over Django TestCase with database interactions for model tests.
📚 Learning: 2026-01-06T21:11:03.537Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:03.537Z
Learning: In backend/tests/apps/mentorship/, prefer pure mock tests (no real database interactions) for model tests. Avoid using Django TestCase with database access; implement tests with mocks/stubs to exercise logic and, when needed, lightweight fixtures without DB. This guideline targets all Python test files under the mentorship module.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py
  • backend/tests/apps/mentorship/model/test_module.py
  • backend/tests/apps/mentorship/model/test_program.py
  • backend/tests/apps/mentorship/model/test_start_end_range.py
📚 Learning: 2025-07-13T05:55:46.436Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/program.py:166-166
Timestamp: 2025-07-13T05:55:46.436Z
Learning: In the OWASP Nest mentorship GraphQL mutations, Strawberry GraphQL automatically converts between Django Program instances and ProgramNode types, so mutations can return Program instances directly without manual conversion even when typed as ProgramNode, similar to the Module/ModuleNode pattern.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py
  • backend/apps/mentorship/api/internal/nodes/mentee.py
  • backend/tests/apps/mentorship/model/test_module.py
  • backend/tests/apps/mentorship/model/test_program.py
  • backend/tests/apps/mentorship/model/test_start_end_range.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py
  • backend/apps/mentorship/api/internal/nodes/mentee.py
  • backend/tests/apps/mentorship/model/test_module.py
  • backend/tests/apps/mentorship/model/test_program.py
  • backend/tests/apps/mentorship/model/test_start_end_range.py
📚 Learning: 2025-09-29T06:02:35.566Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2178
File: frontend/src/components/SingleModuleCard.tsx:54-54
Timestamp: 2025-09-29T06:02:35.566Z
Learning: In the Module type from types/mentorship.ts, the experienceLevel field is required (experienceLevel: ExperienceLevelEnum), not optional, so null/undefined checks are not needed when accessing this field.

Applied to files:

  • backend/apps/mentorship/api/internal/nodes/mentee.py
📚 Learning: 2025-09-10T15:32:12.688Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2211
File: backend/apps/nest/controllers/calendar_events.py:0-0
Timestamp: 2025-09-10T15:32:12.688Z
Learning: In the backend/apps/nest/controllers/calendar_events.py file, the scheduled_time parameter in the schedule_reminder function is guaranteed to be timezone-aware, so explicit timezone awareness validation is not needed.

Applied to files:

  • backend/tests/apps/mentorship/model/test_module.py
📚 Learning: 2025-07-14T16:18:07.287Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/src/components/ModuleForm.tsx:112-134
Timestamp: 2025-07-14T16:18:07.287Z
Learning: In the OWASP Nest mentorship system, date validation for modules (ensuring start date precedes end date) is handled on the backend in the module GraphQL mutations via the `_validate_module_dates` helper function in backend/apps/mentorship/graphql/mutations/module.py, which prevents invalid date ranges from being stored in the database.

Applied to files:

  • backend/tests/apps/mentorship/model/test_module.py
📚 Learning: 2025-07-08T17:24:36.501Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/program.py:41-44
Timestamp: 2025-07-08T17:24:36.501Z
Learning: In the mentorship program GraphQL mutations, date validation is handled at the GraphQL schema/node level in the input types (CreateProgramInput, UpdateProgramInput), preventing null values from reaching the mutation logic where date comparisons are performed.

Applied to files:

  • backend/tests/apps/mentorship/model/test_module.py
🧬 Code graph analysis (4)
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py (1)
backend/apps/mentorship/api/internal/nodes/enum.py (2)
  • ExperienceLevelEnum (12-18)
  • ProgramStatusEnum (22-27)
backend/tests/apps/mentorship/model/test_module.py (2)
backend/apps/mentorship/models/managers/module.py (1)
  • PublishedModuleManager (8-13)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/tests/apps/mentorship/model/test_program.py (1)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/tests/apps/mentorship/model/test_start_end_range.py (1)
backend/apps/mentorship/models/common/start_end_range.py (1)
  • StartEndRange (6-13)
🪛 Ruff (0.14.10)
backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py

1-1: File backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


1-2: 1 blank line required between summary line and description

(D205)


1-2: Multi-line docstring closing quotes should be on a separate line

Move closing quotes to new line

(D209)


21-23: 1 blank line required between summary line and description

(D205)


31-31: datetime.datetime() called without a tzinfo argument

(DTZ001)


34-34: datetime.datetime() called without a tzinfo argument

(DTZ001)


67-67: datetime.datetime() called without a tzinfo argument

(DTZ001)


72-72: datetime.datetime() called without a tzinfo argument

(DTZ001)


96-96: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


97-97: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


100-100: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


107-107: datetime.datetime.now() called without a tz argument

(DTZ005)


109-109: datetime.datetime.now() called without a tz argument

(DTZ005)


117-117: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


118-118: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


119-119: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


123-123: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


132-132: datetime.datetime.now() called without a tz argument

(DTZ005)


134-134: datetime.datetime.now() called without a tz argument

(DTZ005)


144-144: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)


145-145: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)

backend/tests/apps/mentorship/model/test_module.py

1-1: File backend/tests/apps/mentorship/model/test_module.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


39-43: 1 blank line required between summary line and description

(D205)


39-43: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


59-59: Unused function argument: args

(ARG001)


59-59: Unused function argument: kwargs

(ARG001)


88-88: No newline at end of file

Add trailing newline

(W292)

backend/tests/apps/mentorship/model/test_program.py

1-1: File backend/tests/apps/mentorship/model/test_program.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


15-15: No newline at end of file

Add trailing newline

(W292)

backend/tests/apps/mentorship/model/test_start_end_range.py

1-1: File backend/tests/apps/mentorship/model/test_start_end_range.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


9-9: datetime.datetime() called without a tzinfo argument

(DTZ001)


10-10: datetime.datetime() called without a tzinfo argument

(DTZ001)

🔇 Additional comments (2)
backend/apps/mentorship/api/internal/nodes/mentee.py (1)

27-30: LGTM! Ensures consistent string output for experience level.

The change to return self.experience_level.value ensures the resolver consistently yields a string value instead of the enum member itself, which aligns with GraphQL string field expectations. The fallback to "beginner" when the field is not set is preserved.

backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py (1)

20-44: Well-designed test fixture for resolver testing.

The FakeProgramNode class provides a clean way to test the admins resolver pattern in isolation while maintaining control over the underlying manager. This approach effectively validates the resolver logic without requiring a full GraphQL or database setup.

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: 2

🤖 Fix all issues with AI Agents
In @backend/apps/mentorship/api/internal/nodes/mentee.py:
- Line 30: The file ends without a trailing newline on the last line containing
the return expression for the experience level; open the file containing the
method/property that returns "self.experience_level if self.experience_level
else 'beginner'" and add a newline character at the end of the file so the final
line is terminated.
- Around line 27-30: The resolver resolve_experience_level currently declares a
return type of str but the GraphQL field (experienceLevel) is declared as
ExperienceLevelEnum; make them match by either changing the resolver to return
an ExperienceLevelEnum (import and return ExperienceLevelEnum.BEGINNER as the
fallback instead of "beginner") or changing the field declaration to type str;
update the resolver signature to -> ExperienceLevelEnum and ensure the returned
value is an enum member (or change the field's type to str if string returns are
intended).
🧹 Nitpick comments (1)
backend/apps/mentorship/api/internal/nodes/mentee.py (1)

27-30: Remove redundant null check on required field.

Per the retrieved learnings, experience_level is a required field (non-optional). The conditional check at line 30 is unnecessary since the field cannot be None or falsy. Simplify to a direct return.

🔎 Proposed simplification
     @strawberry.field(name="experienceLevel")
     def resolve_experience_level(self) -> str:
         """Get the experience level of the mentee."""
-        return self.experience_level if self.experience_level else "beginner"
+        return self.experience_level
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2113e5 and ffbe683.

📒 Files selected for processing (1)
  • backend/apps/mentorship/api/internal/nodes/mentee.py
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:03.537Z
Learning: In the mentorship test suite (backend/tests/apps/mentorship/), the maintainer kart-u prefers pure mock tests over Django TestCase with database interactions for model tests.
📚 Learning: 2025-09-29T06:02:35.566Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2178
File: frontend/src/components/SingleModuleCard.tsx:54-54
Timestamp: 2025-09-29T06:02:35.566Z
Learning: In the Module type from types/mentorship.ts, the experienceLevel field is required (experienceLevel: ExperienceLevelEnum), not optional, so null/undefined checks are not needed when accessing this field.

Applied to files:

  • backend/apps/mentorship/api/internal/nodes/mentee.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/apps/mentorship/api/internal/nodes/mentee.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/apps/mentorship/api/internal/nodes/mentee.py
🪛 Ruff (0.14.10)
backend/apps/mentorship/api/internal/nodes/mentee.py

30-30: No newline at end of file

Add trailing newline

(W292)

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

🤖 Fix all issues with AI agents
In
@backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py:
- Around line 1-11: Tests import symbols like MenteeNode, MentorshipQuery,
Mentee, MenteeModule, Module and Mentor from the mentorship test package but
several package directories are missing __init__.py so Python treats them as
non-packages; add an empty __init__.py to each package level in the mentorship
test package tree (so the test module that imports MenteeNode and
MentorshipQuery can be discovered and imports resolve) and re-run tests to
ensure the imports in test_api_queries_mentorship.py work.
🧹 Nitpick comments (7)
backend/tests/apps/mentorship/model/test_module.py (3)

53-53: Remove or prefix unused function arguments.

The args and kwargs parameters are not used in simulate_save_for_inheritance. Either remove them or prefix with an underscore to indicate they're intentionally unused.

✨ Proposed fix
-        def simulate_save_for_inheritance(*args, **kwargs):
+        def simulate_save_for_inheritance(*_args, **_kwargs):
             mock_module.key = "date-module"  
             if not mock_module.started_at:
                 mock_module.started_at = mock_module.program.started_at
             if not mock_module.ended_at:
                 mock_module.ended_at = mock_module.program.ended_at

Or remove them entirely if not needed:

-        def simulate_save_for_inheritance(*args, **kwargs):
+        def simulate_save_for_inheritance():
             mock_module.key = "date-module"  
             if not mock_module.started_at:
                 mock_module.started_at = mock_module.program.started_at
             if not mock_module.ended_at:
                 mock_module.ended_at = mock_module.program.ended_at

33-37: Fix docstring formatting.

The docstring should follow PEP 257 conventions: summary line should start at the first line and be separated from the description by a blank line.

📝 Proposed fix
-    def test_module_save(self, mock_create_module):
-        """
-        We cannot call the real Model.save without DB/app registry here,
-        so simulate the save behavior by attaching a side_effect to a mock instance's save method.
-        This verifies the *expected* effects (key slugification and date inheritance) in a pure-mock way.
-        """
+    def test_module_save(self, mock_create_module):
+        """Test Module.save behavior with pure mocks.
+
+        We cannot call the real Model.save without DB/app registry here,
+        so simulate the save behavior by attaching a side_effect to a mock instance's save method.
+        This verifies the *expected* effects (key slugification and date inheritance) in a pure-mock way.
+        """

82-82: Add newline at end of file.

The file is missing a trailing newline, which is a common Python convention.

📝 Proposed fix
         assert module.started_at == explicit_start
         assert module.ended_at == explicit_end
         assert module.key == "date-module"
+
backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py (4)

81-98: Minor formatting: add spaces around = in attribute assignments.

For consistency with PEP 8 style, add spaces around the assignment operator when setting mock attributes.

🔧 Suggested formatting
         mock_module = MagicMock(spec=Module)
-        mock_module.id=1
+        mock_module.id = 1
         mock_module_only.return_value.get.return_value = mock_module

         mock_github_user = MagicMock(spec=GithubUser)
-        mock_github_user.login="testmentee"
-        mock_github_user.name="Test_Mentee" 
-        mock_github_user.avatar_url="url"
-        mock_github_user.bio="bio"
+        mock_github_user.login = "testmentee"
+        mock_github_user.name = "Test_Mentee"
+        mock_github_user.avatar_url = "url"
+        mock_github_user.bio = "bio"

         mock_github_user_only.return_value.get.return_value = mock_github_user

         mock_mentee = MagicMock(spec=Mentee)
-        mock_mentee.id=2
-        mock_mentee.experience_level="Mid"
-        mock_mentee.domains=["Web"]
-        mock_mentee.tags=["Python"]
+        mock_mentee.id = 2
+        mock_mentee.experience_level = "Mid"
+        mock_mentee.domains = ["Web"]
+        mock_mentee.tags = ["Python"]

243-243: Consider using assert_any_call or verifying call count for clarity.

GithubUser.objects.only is called twice in get_mentee_module_issues: once with ("id") for the main lookup and once with ("id", "login", "name", "avatar_url") in the Prefetch. Using assert_called_with only verifies the last call matched. For explicit verification of both calls, consider:

# Option 1: Verify both calls explicitly
assert mock_github_user_only.call_count == 2
mock_github_user_only.assert_any_call("id")
mock_github_user_only.assert_any_call("id", "login", "name", "avatar_url")

346-348: Remove unused mock variables.

Static analysis correctly identifies that mock_issue1 and mock_issue3 are defined but never used in the test. Since only mock_issue2 appears in the slice, remove the unused variables.

♻️ Proposed fix
-        mock_issue1 = MagicMock(id=1, number=1, title="Issue 1", state="open", created_at="", url="")
         mock_issue2 = MagicMock(id=2, number=2, title="Issue 2", state="closed", created_at="", url="")
-        mock_issue3 = MagicMock(id=3, number=3, title="Issue 3", state="open", created_at="", url="")

206-206: Minor: add space after colon in type hint.

-        mock_prefetch:MagicMock
+        mock_prefetch: MagicMock

The same applies to lines 334 and 384 where mock_prefetch.return_value=MagicMock() should have spaces around =.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ffbe683 and 73a3340.

📒 Files selected for processing (2)
  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py
  • backend/tests/apps/mentorship/model/test_module.py
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:03.537Z
Learning: In the mentorship test suite (backend/tests/apps/mentorship/), the maintainer kart-u prefers pure mock tests over Django TestCase with database interactions for model tests.
📚 Learning: 2026-01-06T21:11:03.537Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:03.537Z
Learning: In backend/tests/apps/mentorship/, prefer pure mock tests (no real database interactions) for model tests. Avoid using Django TestCase with database access; implement tests with mocks/stubs to exercise logic and, when needed, lightweight fixtures without DB. This guideline targets all Python test files under the mentorship module.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py
  • backend/tests/apps/mentorship/model/test_module.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py
  • backend/tests/apps/mentorship/model/test_module.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py
  • backend/tests/apps/mentorship/model/test_module.py
📚 Learning: 2025-09-10T15:32:12.688Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2211
File: backend/apps/nest/controllers/calendar_events.py:0-0
Timestamp: 2025-09-10T15:32:12.688Z
Learning: In the backend/apps/nest/controllers/calendar_events.py file, the scheduled_time parameter in the schedule_reminder function is guaranteed to be timezone-aware, so explicit timezone awareness validation is not needed.

Applied to files:

  • backend/tests/apps/mentorship/model/test_module.py
📚 Learning: 2025-07-14T16:18:07.287Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/src/components/ModuleForm.tsx:112-134
Timestamp: 2025-07-14T16:18:07.287Z
Learning: In the OWASP Nest mentorship system, date validation for modules (ensuring start date precedes end date) is handled on the backend in the module GraphQL mutations via the `_validate_module_dates` helper function in backend/apps/mentorship/graphql/mutations/module.py, which prevents invalid date ranges from being stored in the database.

Applied to files:

  • backend/tests/apps/mentorship/model/test_module.py
📚 Learning: 2025-07-08T17:24:36.501Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/program.py:41-44
Timestamp: 2025-07-08T17:24:36.501Z
Learning: In the mentorship program GraphQL mutations, date validation is handled at the GraphQL schema/node level in the input types (CreateProgramInput, UpdateProgramInput), preventing null values from reaching the mutation logic where date comparisons are performed.

Applied to files:

  • backend/tests/apps/mentorship/model/test_module.py
🧬 Code graph analysis (2)
backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py (1)
backend/apps/mentorship/api/internal/queries/mentorship.py (3)
  • is_mentor (36-48)
  • get_mentee_details (51-82)
  • get_mentee_module_issues (85-124)
backend/tests/apps/mentorship/model/test_module.py (2)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/tests/apps/mentorship/model/test_program.py (1)
  • test_str_returns_name (13-15)
🪛 Ruff (0.14.10)
backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py

1-1: File backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


346-346: Local variable mock_issue1 is assigned to but never used

Remove assignment to unused variable mock_issue1

(F841)


348-348: Local variable mock_issue3 is assigned to but never used

Remove assignment to unused variable mock_issue3

(F841)

backend/tests/apps/mentorship/model/test_module.py

1-1: File backend/tests/apps/mentorship/model/test_module.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


33-37: 1 blank line required between summary line and description

(D205)


33-37: Multi-line docstring summary should start at the first line

Remove whitespace after opening quotes

(D212)


53-53: Unused function argument: args

(ARG001)


53-53: Unused function argument: kwargs

(ARG001)


82-82: No newline at end of file

Add trailing newline

(W292)

🔇 Additional comments (4)
backend/tests/apps/mentorship/model/test_module.py (2)

11-21: LGTM!

The setup correctly uses timezone-aware datetimes and properly mocks the required models.


23-28: LGTM!

The pure mock test correctly validates the __str__ implementation.

backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py (2)

13-63: LGTM!

The TestIsMentor class provides comprehensive coverage of the is_mentor query, including edge cases for empty/whitespace/None logins. The parametrized test is a clean approach for testing multiple invalid inputs. Based on learnings, the pure mock approach aligns with the preferred testing style for this module.


192-408: Good test coverage for get_mentee_module_issues.

The test class comprehensively covers the success path, all exception scenarios (module/user/mentee not found, not enrolled), and pagination edge cases including out-of-range offsets. The mock chain for module.issues.filter().only().prefetch_related().order_by() correctly simulates the QuerySet behavior.

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

🧹 Nitpick comments (11)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py (1)

39-51: Fix the parametrize decorator to use a tuple.

The first argument to pytest.mark.parametrize should be a tuple of parameter names, not a comma-separated string. While pytest accepts strings for historical reasons, using a tuple is the recommended approach.

♻️ Proposed fix
 @pytest.mark.parametrize(
-    "repo_url, expected",
+    ("repo_url", "expected"),
     [
         ("https://github.com/owner/repo", "owner/repo"),
backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py (6)

4-5: Remove unused datetime imports.

Neither datetime nor dt are used in this file.

♻️ Proposed fix
-from datetime import datetime
-import datetime as dt
-

10-10: Consider making exists a keyword-only parameter.

Boolean flags as positional arguments reduce readability. Making it keyword-only (add * before the parameter) ensures callers must explicitly write exists=False, improving clarity.

♻️ Proposed fix
-def make_qs(iterable, exists=True):
+def make_qs(iterable, *, exists=True):

29-34: Consider using a realistic default for created_at.

The fallback "now" is a string, but the actual code likely expects a datetime object. Using a more realistic mock default (e.g., datetime.now() or a fixed datetime) would make tests more robust.


88-88: Rename mock_issue_1 to clarify it patches the Issue class.

The name mock_issue_1 suggests a specific issue instance, but it patches the Issue class (model). Combined with the mock_issue fixture parameter, this creates confusion. Consider renaming to mock_issue_class or mock_issue_model.

♻️ Proposed fix
-def test_process_module(mock_issue_1, mock_process_issue_interests, mock_sync_issue_comments, mock_get_github_client, command, mock_module, mock_issue):
+def test_process_module(mock_issue_class, mock_process_issue_interests, mock_sync_issue_comments, mock_get_github_client, command, mock_module, mock_issue):
     """Test process_module orchestrates issue syncing and interest processing."""
     mock_issue.id = 1
     mock_issue.title = "Test Issue 1"
     mock_issue.number = 123
 
     issues_qs = make_qs([mock_issue], exists=True)
-    mock_issue_1.objects.filter.return_value.distinct.return_value = issues_qs
+    mock_issue_class.objects.filter.return_value.distinct.return_value = issues_qs

124-128: Simplify fragile assertion chain.

The triple index call_args[0][0][0] is hard to follow. Consider using intermediate variables or call_args.args[0][0] for better clarity.

♻️ Proposed fix
     mock_issue_user_interest.objects.bulk_create.assert_called_once()
-    created_interest = mock_issue_user_interest.objects.bulk_create.call_args[0][0][0]
+    bulk_create_call_args = mock_issue_user_interest.objects.bulk_create.call_args.args
+    interests_list = bulk_create_call_args[0]
+    created_interest = interests_list[0]
     assert created_interest.module == mock_module

134-186: Consider consolidating duplicate test cases.

Both test_process_issue_interests_remove_interest (lines 134-156) and test_process_issue_interests_existing_interest_removed (lines 159-186) test the same scenario: a user with an existing interest posts a comment that doesn't match interest patterns, resulting in interest removal. The only difference is the comment body text. Consider using pytest.mark.parametrize to avoid duplication.

♻️ Proposed refactor using parametrize
+@pytest.mark.parametrize("comment_body", [
+    "Not interested anymore",
+    "Just a regular comment",
+])
 @patch("apps.mentorship.management.commands.mentorship_update_comments.IssueUserInterest")
-def test_process_issue_interests_remove_interest(mock_issue_user_interest, command, mock_issue, mock_module):
-    """Test process_issue_interests correctly removes interests."""
+def test_process_issue_interests_remove_interest(comment_body, mock_issue_user_interest, command, mock_issue, mock_module):
+    """Test process_issue_interests removes interests when user posts non-interested comment."""
     user1 = make_user(1, "user1")
-    comment1 = make_comment(body="Not interested anymore", author=user1, created_at="2023-01-01")
+    comment1 = make_comment(body=comment_body, author=user1, created_at="2023-01-01")
 
     comments_qs = make_qs([comment1], exists=True)
     mock_issue.comments.select_related.return_value.filter.return_value.order_by.return_value = comments_qs
 
     mock_issue_user_interest.objects.filter.return_value.values_list.return_value = [1]
     mock_issue_user_interest.objects.filter.return_value.delete.return_value = (1, {})
 
     mock_issue_user_interest.side_effect = lambda **kwargs: SimpleNamespace(**kwargs)
     mock_issue_user_interest.objects.bulk_create = MagicMock()
 
     command.process_issue_interests(mock_issue, mock_module)
 
     mock_issue_user_interest.objects.bulk_create.assert_not_called()
-    mock_issue_user_interest.objects.filter.assert_any_call(module=mock_module, issue=mock_issue, user_id__in=[1])
+    
+    filter_calls = mock_issue_user_interest.objects.filter.call_args_list
+    found_delete_filter = any(
+        c.kwargs.get("user_id__in") == [1] and c.kwargs.get("module") == mock_module
+        for c in filter_calls
+    )
+    assert found_delete_filter
+    
     mock_issue_user_interest.objects.filter.return_value.delete.assert_called_once()
-    command.stdout.write.assert_any_call(
-        command.style.WARNING("Unregistered 1 interest(s) for issue #123: user1")
-    )
-
-
-@patch("apps.mentorship.management.commands.mentorship_update_comments.IssueUserInterest")
-def test_process_issue_interests_existing_interest_removed(
-    mock_issue_user_interest, command, mock_issue, mock_module
-):
-    """Existing interest should be removed when user no longer shows interest."""
-    mock_issue.number = 1
-
-    user1 = make_user(1, "user1")
-    comment1 = make_comment(body="Just a regular comment", author=user1, created_at="2023-01-01")
-
-    comments_qs = make_qs([comment1], exists=True)
-    mock_issue.comments.select_related.return_value.filter.return_value.order_by.return_value = comments_qs
-
-    mock_issue_user_interest.objects.filter.return_value.values_list.return_value = [1]
-    mock_issue_user_interest.objects.filter.return_value.delete.return_value = (1, {})
-
-    mock_issue_user_interest.side_effect = lambda **kwargs: SimpleNamespace(**kwargs)
-    mock_issue_user_interest.objects.bulk_create = MagicMock()
-
-    command.process_issue_interests(mock_issue, mock_module)
-
-    mock_issue_user_interest.objects.bulk_create.assert_not_called()
-
-    mock_issue_user_interest.objects.filter.return_value.delete.assert_called_once()
-    command.stdout.write.assert_any_call(
-        command.style.WARNING("Unregistered 1 interest(s) for issue #1: user1")
-    )

Note: You could then remove the duplicate test function.

backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py (4)

17-23: Consider minor style improvements.

The helper function works correctly but has two minor style issues flagged by static analysis:

  1. Boolean positional argument exists=True — consider making it keyword-only
  2. Docstring should use imperative mood
✨ Optional style refinements
-def make_qs(iterable, exists=True):
-    """Helper: return a queryset-like MagicMock that is iterable and supports .exists()."""
+def make_qs(iterable, *, exists=True):
+    """Return a queryset-like MagicMock that is iterable and supports .exists()."""
     qs = MagicMock(name="QuerySet")
     qs.exists.return_value = exists
     qs.__iter__.return_value = iter(iterable)
     qs.all.return_value = list(iterable)
     return qs

26-34: Remove blank line after docstring.

Static analysis flags an unnecessary blank line after the docstring on line 29.

✨ Style fix
 @patch("apps.mentorship.management.commands.mentorship_sync_issue_levels.TaskLevel")
 def test_handle_no_task_levels(mock_task_level, command):
     """When no TaskLevel objects exist, command should exit early with a warning."""
-    
     empty_qs = make_qs([], exists=False)
     mock_task_level.objects.select_related.return_value.order_by.return_value = empty_qs

52-54: Split multiple statements to separate lines.

Multiple statements are combined on single lines using semicolons, which reduces readability.

✨ Style fix
-    label_a = MagicMock(); label_a.name = "Label-A"
-    label_b = MagicMock(); label_b.name = "Label-B"
-    label_nomatch = MagicMock(); label_nomatch.name = "No-Match"
+    label_a = MagicMock()
+    label_a.name = "Label-A"
+    label_b = MagicMock()
+    label_b.name = "Label-B"
+    label_nomatch = MagicMock()
+    label_nomatch.name = "No-Match"

And similarly for line 106:

-    label_a = MagicMock(); label_a.name = "Label-A"
+    label_a = MagicMock()
+    label_a.name = "Label-A"

Also applies to: 106-106


95-99: Remove blank line after docstring.

Static analysis flags an unnecessary blank line after the docstring.

✨ Style fix
 def test_handle_no_updates_needed(mock_task_level, mock_issue, command):
     """When all issues already have the correct level, bulk_update is not called."""
     mock_module = MagicMock()
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a6fe81 and 80ceb84.

📒 Files selected for processing (7)
  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py
  • backend/tests/apps/mentorship/management/__init__.py
  • backend/tests/apps/mentorship/management/commands/__init__.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
  • backend/tests/apps/mentorship/model/__init__.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:08.745Z
Learning: In the mentorship test suite (backend/tests/apps/mentorship/), the maintainer kart-u prefers pure mock tests over Django TestCase with database interactions for model tests.
📚 Learning: 2026-01-06T21:11:03.537Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:03.537Z
Learning: In backend/tests/apps/mentorship/, prefer pure mock tests (no real database interactions) for model tests. Avoid using Django TestCase with database access; implement tests with mocks/stubs to exercise logic and, when needed, lightweight fixtures without DB. This guideline targets all Python test files under the mentorship module.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py
  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 2000
File: backend/apps/mentorship/management/commands/sync_module_issues.py:109-145
Timestamp: 2025-08-17T11:55:55.990Z
Learning: In the OWASP/Nest mentorship system, tasks are designed to be assigned to only one assignee per issue, even if GitHub issues can have multiple assignees. The sync_module_issues command correctly uses issue.assignees.first() to create one task per issue for the first assignee only.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py
🧬 Code graph analysis (3)
backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py (1)
backend/apps/mentorship/management/commands/mentorship_update_comments.py (3)
  • process_mentorship_modules (31-55)
  • process_module (57-89)
  • process_issue_interests (91-160)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py (2)
backend/tests/apps/owasp/management/commands/owasp_aggregate_contributions_test.py (3)
  • select_related (36-38)
  • order_by (32-34)
  • prefetch_related (40-42)
backend/apps/github/api/internal/nodes/issue.py (1)
  • labels (50-52)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py (1)
backend/apps/mentorship/management/commands/mentorship_sync_module_issues.py (5)
  • _extract_repo_full_name (28-40)
  • _get_status (42-50)
  • _get_last_assigned_date (52-74)
  • _build_repo_label_to_issue_map (76-92)
  • _process_module (94-203)
🪛 Ruff (0.14.10)
backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py

10-10: Boolean default positional argument in function definition

(FBT002)

backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py

17-17: Boolean default positional argument in function definition

(FBT002)


18-18: First line of docstring should be in imperative mood: "Helper: return a queryset-like MagicMock that is iterable and supports .exists()."

(D401)


28-28: No blank lines allowed after function docstring (found 1)

Remove blank line(s) after function docstring

(D202)


40-40: No blank lines allowed after function docstring (found 1)

Remove blank line(s) after function docstring

(D202)


52-52: Multiple statements on one line (semicolon)

(E702)


53-53: Multiple statements on one line (semicolon)

(E702)


54-54: Multiple statements on one line (semicolon)

(E702)


106-106: Multiple statements on one line (semicolon)

(E702)

backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py

12-12: Boolean default positional argument in function definition

(FBT002)


40-40: Wrong type passed to first argument of pytest.mark.parametrize; expected tuple

Use a tuple for the first argument

(PT006)


55-55: Multiple statements on one line (semicolon)

(E702)


68-68: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


69-69: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


70-70: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


71-71: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


75-75: Use datetime.UTC alias

Convert to datetime.UTC alias

(UP017)


115-115: Multiple statements on one line (semicolon)

(E702)


115-115: Multiple statements on one line (semicolon)

(E702)


120-120: Multiple statements on one line (semicolon)

(E702)


120-120: Multiple statements on one line (semicolon)

(E702)


127-127: Multiple statements on one line (semicolon)

(E702)


186-186: Multiple statements on one line (semicolon)

(E702)


186-186: Multiple statements on one line (semicolon)

(E702)

🔇 Additional comments (8)
backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py (6)

21-30: LGTM!

The command fixture is well-structured, properly mocking stdout, stderr, and style methods for isolated command testing.


54-60: LGTM!

The test comprehensively covers all status mapping scenarios (closed, open with assignee, open without assignee).


63-85: LGTM!

Excellent coverage of the assigned date retrieval logic, including the happy path (finding the latest assignment), not-found case, and exception handling with stderr output.


88-109: LGTM!

This test effectively validates the repo-label map building logic, including case-insensitive label normalization and proper aggregation across multiple repositories.


112-182: LGTM!

This comprehensive test validates the core module processing logic including:

  • Issue linkage via module.issues.set
  • Task creation with correct status mapping (IN_PROGRESS for open+assigned, COMPLETED for closed)
  • Assigned date retrieval and updating
  • Success message output

The assertion for num_linked (line 158) correctly verifies the return value. All past review concerns have been addressed.


185-208: LGTM!

This test correctly validates the edge case where no issues match the module's labels, ensuring that:

  • An empty set is assigned to module.issues
  • No tasks are created
  • The return value correctly indicates zero linked issues

Past signature issues have been resolved.

backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py (1)

214-259: Comprehensive multi-user interest test coverage.

This test effectively validates the complex scenario of simultaneous interest creation and removal across multiple users. The assertions correctly verify both the bulk_create and delete operations, ensuring the command handles mixed interest states properly.

backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py (1)

7-14: LGTM!

The fixture correctly mocks the command's stdout and style attributes for testing Django management command output.

@kart-u
Copy link
Contributor Author

kart-u commented Jan 7, 2026

@coderabbitai generate docstrings

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 7, 2026

✅ Actions performed

Initiated docstring generation; will generate only if new commits exist.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 7, 2026

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #3237

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 7, 2026
coderabbitai bot added a commit that referenced this pull request Jan 7, 2026
Docstrings generation was requested by @kart-u.

* #3179 (comment)

The following files were modified:

* `backend/apps/mentorship/api/internal/nodes/mentee.py`
* `backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_mentor.py`
* `backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_module.py`
* `backend/tests/apps/mentorship/api/internal/nodes/test_api_internal_program.py`
* `backend/tests/apps/mentorship/api/internal/queries/test_api_queries_mentorship.py`
* `backend/tests/apps/mentorship/api/internal/queries/test_api_queries_program.py`
* `backend/tests/apps/mentorship/management/commands/test_mentorship_sync_issue_levels.py`
* `backend/tests/apps/mentorship/management/commands/test_mentorship_sync_module_issues.py`
* `backend/tests/apps/mentorship/management/commands/test_mentorship_update_comments.py`
* `backend/tests/apps/mentorship/model/test_module.py`
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: 3

🤖 Fix all issues with AI agents
In
@backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py:
- Around line 312-328: The test `test_module_node_interested_users_no_issue`
incorrectly contains unrelated `task_deadline` assertions; remove the block that
patches `apps.mentorship.models.task.Task.objects` and the subsequent `deadline`
assertions from that function, and create a new standalone test function named
`test_module_node_task_deadline` that contains the patched `Task.objects` setup,
the call to `mock_module_node.task_deadline(issue_number=101)`, the assert on
the returned datetime, and the
`mock_task_objects.filter.assert_called_once_with(...)` verification; keep the
same mock setup and assertions but located in the new function so
`test_module_node_interested_users_no_issue` only tests `interested_users`.

In
@backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py:
- Around line 11-20: The docstring for make_qs is incomplete and cuts off
mid-sentence; update the docstring of the make_qs function to fully describe
what the mock queryset does (e.g., "Return a queryset-like MagicMock that is
iterable and supports exists(), all(), count(), distinct(), and iteration"), so
it clearly documents the behaviors implemented by the MagicMock (exists,
__iter__, all, count, distinct).
🧹 Nitpick comments (14)
backend/tests/apps/mentorship/model/task_level_test.py (1)

17-26: Consider whether this test adds sufficient value.

This test verifies that TaskLevel.__str__ uses module.name directly rather than str(module). While the test is correct, it provides limited additional value since the implementation explicitly accesses the .name attribute:

def __str__(self) -> str:
    return f"{self.module.name} - {self.name}"

The first test already covers the essential behavior. This test primarily documents implementation details that are unlikely to change or cause issues.

backend/tests/apps/mentorship/model/task_test.py (2)

9-21: Consider using idiomatic str() for string representation tests.

Directly calling Task.__str__(task) works but is non-idiomatic. Using str(task) is more natural and tests the actual behavior users experience.

♻️ Proposed refactor
-        expected = f"Task: '{issue.title}' assigned to {assignee.login}"
-        assert Task.__str__(task) == expected
+        expected = f"Task: '{issue.title}' assigned to {assignee.login}"
+        assert str(task) == expected

23-32: Consider using idiomatic str() for string representation tests.

Same as the previous test method - using str(task) instead of Task.__str__(task) would be more idiomatic and natural.

♻️ Proposed refactor
-        expected = f"Task: {issue.title} (Unassigned)"
-        assert Task.__str__(task) == expected
+        expected = f"Task: {issue.title} (Unassigned)"
+        assert str(task) == expected
backend/tests/apps/mentorship/model/start_end_range_test.py (1)

17-17: Remove redundant variable assignment.

The assignment instance = mock_instance serves no purpose—both variables reference the same mock. Use mock_instance directly in the assertions below to simplify the code.

♻️ Proposed fix
     mock_instance = MagicMock(spec=StartEndRange)
     mock_instance.started_at = start_time
     mock_instance.ended_at = end_time
-    instance = mock_instance

-    assert instance.started_at == start_time
-    assert instance.ended_at == end_time
+    assert mock_instance.started_at == start_time
+    assert mock_instance.ended_at == end_time
backend/tests/apps/mentorship/model/module_test.py (1)

32-92: Consider splitting into two separate test methods for clarity.

The test correctly verifies two scenarios:

  1. Default date inheritance from the program (lines 61-72)
  2. Explicit dates are preserved (lines 74-92)

Both scenarios work correctly within the current structure, but splitting them into separate test methods (test_module_inherits_program_dates and test_module_preserves_explicit_dates) would improve readability and follow the single-responsibility principle for unit tests.

♻️ Optional refactor to split test scenarios

Split into two methods:

@patch("apps.mentorship.models.Module.objects.create")
def test_module_inherits_program_dates_when_not_provided(self, mock_create_module):
    """Test that module inherits program dates when started_at/ended_at are None."""
    program_with_dates = MagicMock(
        spec=Program,
        name="Program With Dates",
        key="program-with-dates",
        started_at=django.utils.timezone.datetime(
            2024, 2, 1, tzinfo=django.utils.timezone.UTC
        ),
        ended_at=django.utils.timezone.datetime(2024, 2, 28, tzinfo=django.utils.timezone.UTC),
    )

    mock_module = MagicMock(spec=Module)
    mock_module.name = "Auto Date Module"
    mock_module.program = program_with_dates
    mock_module.project = self.project
    mock_module.started_at = None
    mock_module.ended_at = None

    def simulate_save_for_inheritance():
        mock_module.key = "date-module"
        if not mock_module.started_at:
            mock_module.started_at = mock_module.program.started_at
        if not mock_module.ended_at:
            mock_module.ended_at = mock_module.program.ended_at

    mock_module.save.side_effect = simulate_save_for_inheritance
    mock_create_module.return_value = mock_module

    module = Module.objects.create(
        name="Auto Date Module", program=program_with_dates, project=self.project
    )
    module.save()

    assert module.key == "date-module"
    assert module.started_at == django.utils.timezone.datetime(
        2024, 2, 1, tzinfo=django.utils.timezone.UTC
    )
    assert module.ended_at == django.utils.timezone.datetime(
        2024, 2, 28, tzinfo=django.utils.timezone.UTC
    )

@patch("apps.mentorship.models.Module.objects.create")
def test_module_preserves_explicit_dates(self, mock_create_module):
    """Test that module preserves explicitly provided dates."""
    program_with_dates = MagicMock(
        spec=Program,
        name="Program With Dates",
        key="program-with-dates",
        started_at=django.utils.timezone.datetime(
            2024, 2, 1, tzinfo=django.utils.timezone.UTC
        ),
        ended_at=django.utils.timezone.datetime(2024, 2, 28, tzinfo=django.utils.timezone.UTC),
    )

    explicit_start = django.utils.timezone.datetime(
        2024, 3, 1, tzinfo=django.utils.timezone.UTC
    )
    explicit_end = django.utils.timezone.datetime(
        2024, 3, 31, tzinfo=django.utils.timezone.UTC
    )

    mock_module = MagicMock(spec=Module)
    mock_module.name = "Auto Date Module"
    mock_module.program = program_with_dates
    mock_module.project = self.project
    mock_module.started_at = explicit_start
    mock_module.ended_at = explicit_end

    def simulate_save_for_inheritance():
        mock_module.key = "date-module"
        if not mock_module.started_at:
            mock_module.started_at = mock_module.program.started_at
        if not mock_module.ended_at:
            mock_module.ended_at = mock_module.program.ended_at

    mock_module.save.side_effect = simulate_save_for_inheritance
    mock_create_module.return_value = mock_module

    module = Module.objects.create(
        name="Auto Date Module", program=program_with_dates, project=self.project
    )
    module.save()

    assert module.started_at == explicit_start
    assert module.ended_at == explicit_end
    assert module.key == "date-module"
backend/tests/apps/mentorship/management/commands/mentorship_sync_module_issues_test.py (2)

1-2: Consider consolidating datetime imports.

Both the module (import datetime as dt) and the class (from datetime import datetime) are imported. You could simplify by using one form consistently, e.g., from datetime import datetime, UTC and replacing dt.UTC with UTC.

♻️ Proposed refactor
-import datetime as dt
-from datetime import datetime
+from datetime import UTC, datetime

Then update line 70:

-    e1 = MagicMock(event="commented", created_at=datetime(2023, 1, 1, tzinfo=dt.UTC))
+    e1 = MagicMock(event="commented", created_at=datetime(2023, 1, 1, tzinfo=UTC))

130-214: Consider splitting this complex test.

This test validates multiple behaviors: issue linking, task creation, status mapping, and output messaging. While comprehensive, it might be easier to debug and maintain if split into focused tests (e.g., separate tests for status assignment vs. task creation vs. output messaging).

Based on learnings, this approach aligns with the preference for pure mock tests.

backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py (2)

8-15: Consider adding stderr mock for completeness.

The command fixture mocks stdout and style but omits stderr. For consistency with other management command tests in this module (see mentorship_sync_module_issues_test.py), consider adding:

cmd.stderr = MagicMock()

While the current command may not use stderr, including it improves test robustness and follows the established pattern.

Proposed addition
 @pytest.fixture
 def command():
     cmd = Command()
     cmd.stdout = MagicMock()
+    cmd.stderr = MagicMock()
     cmd.style = MagicMock()
     cmd.style.WARNING = lambda x: x
     cmd.style.SUCCESS = lambda x: x
     return cmd

18-24: Consider extracting make_qs to a shared test utilities module.

The make_qs helper is duplicated across multiple test files in backend/tests/apps/mentorship/management/commands/ (appears in at least mentorship_sync_module_issues_test.py and mentorship_update_comments_test.py). Extracting it to a shared test utilities module (e.g., backend/tests/apps/mentorship/management/commands/test_utils.py) would improve maintainability and reduce duplication.

backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py (2)

92-126: Consider renaming parameter for clarity.

The test correctly verifies the orchestration of issue syncing and interest processing. However, the parameter name mock_issue_1 (line 97) is potentially confusing since it represents the patched Issue class, not an issue instance. The actual issue instance is mock_issue (from the fixture).

♻️ Optional clarity improvement
 @patch("apps.mentorship.management.commands.mentorship_update_comments.get_github_client")
 @patch("apps.mentorship.management.commands.mentorship_update_comments.sync_issue_comments")
 @patch.object(Command, "process_issue_interests")
 @patch("apps.mentorship.management.commands.mentorship_update_comments.Issue")
 def test_process_module(
-    mock_issue_1,
+    mock_issue_class,
     mock_process_issue_interests,
     mock_sync_issue_comments,
     mock_get_github_client,
     command,
     mock_module,
     mock_issue,
 ):
     """Test process_module orchestrates issue syncing and interest processing."""
     mock_issue.id = 1
     mock_issue.title = "Test Issue 1"
     mock_issue.number = 123
 
     issues_qs = make_qs([mock_issue], exist=True)
-    mock_issue_1.objects.filter.return_value.distinct.return_value = issues_qs
+    mock_issue_class.objects.filter.return_value.distinct.return_value = issues_qs

189-218: Consider consolidating similar tests.

This test is very similar to test_process_issue_interests_remove_interest (lines 158-187). Both verify interest removal when a user's comment doesn't match the interest pattern. The main difference is the comment body text ("Just a regular comment" vs "Not interested anymore"), but both should trigger the same removal logic.

If these tests are intentionally covering different edge cases, consider adding a comment explaining the distinction. Otherwise, they could potentially be consolidated or parameterized.

backend/tests/apps/mentorship/model/program_test.py (1)

10-12: Consider using a proper mock and idiomatic str call.

Two minor improvements:

  1. The variable name mock_program_instance is misleading—it's a real Program instance, not a mock. Based on learnings, pure mock tests are preferred for the mentorship module.
  2. Calling Program.__str__(mock_program_instance) is unconventional; use str(mock_program_instance) instead.
♻️ Proposed refactor using a proper mock
-    def test_str_returns_name(self):
-        mock_program_instance = Program(name="Security Program")
-        assert Program.__str__(mock_program_instance) == "Security Program"
+    def test_str_returns_name(self):
+        from unittest.mock import MagicMock
+        mock_program = MagicMock(spec=Program)
+        mock_program.name = "Security Program"
+        assert Program.__str__(mock_program) == "Security Program"

Or use the more idiomatic approach:

-    def test_str_returns_name(self):
-        mock_program_instance = Program(name="Security Program")
-        assert Program.__str__(mock_program_instance) == "Security Program"
+    def test_str_returns_name(self):
+        from unittest.mock import MagicMock
+        mock_program = MagicMock(spec=Program)
+        mock_program.name = "Security Program"
+        mock_program.__str__ = lambda: mock_program.name
+        assert str(mock_program) == "Security Program"

Based on learnings, pure mock tests are preferred in the mentorship test suite.

backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (1)

1-141: Consider simplifying FakeModuleNode to use only mock data.

The FakeModuleNode class contains actual Django ORM code in methods like mentees(), issue_mentees(), available_labels(), etc. While these are fully mocked in the fixture, it would be cleaner to have these methods return mock data directly rather than containing real ORM code.

Based on learnings, the maintainer prefers pure mock tests. The current implementation works because everything is mocked in the fixture, but having ORM code in the test double is a code smell.

backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py (1)

282-282: Consider more explicit assertion for multiple ORM calls.

Line 282 uses assert_called_with which verifies at least one call with those arguments, but doesn't check the call count. Since GithubUser.objects.only() is called twice in the production code (once directly with ("id") and once in the Prefetch with ("id", "login", "name", "avatar_url")), consider using assert_any_call for clarity or verifying both calls explicitly.

Current implementation works correctly but could be more explicit about the multiple calls to improve test clarity and maintainability.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80ceb84 and 907ac29.

📒 Files selected for processing (21)
  • backend/apps/github/models/__init__.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_enum_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentee_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_module_issues_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
  • backend/tests/apps/mentorship/model/experience_level_test.py
  • backend/tests/apps/mentorship/model/mentee_test.py
  • backend/tests/apps/mentorship/model/mentor_module_test.py
  • backend/tests/apps/mentorship/model/mentor_test.py
  • backend/tests/apps/mentorship/model/module_test.py
  • backend/tests/apps/mentorship/model/program_test.py
  • backend/tests/apps/mentorship/model/start_end_range_test.py
  • backend/tests/apps/mentorship/model/task_level_test.py
  • backend/tests/apps/mentorship/model/task_test.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/apps/github/models/init.py
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:08.745Z
Learning: In the mentorship test suite (backend/tests/apps/mentorship/), the maintainer kart-u prefers pure mock tests over Django TestCase with database interactions for model tests.
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_enum_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py
  • backend/tests/apps/mentorship/model/task_level_test.py
  • backend/tests/apps/mentorship/model/mentor_module_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py
  • backend/tests/apps/mentorship/model/start_end_range_test.py
  • backend/tests/apps/mentorship/model/mentee_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py
  • backend/tests/apps/mentorship/model/experience_level_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_module_issues_test.py
  • backend/tests/apps/mentorship/model/task_test.py
  • backend/tests/apps/mentorship/model/module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentee_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py
  • backend/tests/apps/mentorship/model/mentor_test.py
  • backend/tests/apps/mentorship/model/program_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_enum_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py
  • backend/tests/apps/mentorship/model/task_level_test.py
  • backend/tests/apps/mentorship/model/mentor_module_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py
  • backend/tests/apps/mentorship/model/start_end_range_test.py
  • backend/tests/apps/mentorship/model/mentee_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py
  • backend/tests/apps/mentorship/model/experience_level_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_module_issues_test.py
  • backend/tests/apps/mentorship/model/task_test.py
  • backend/tests/apps/mentorship/model/module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentee_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py
  • backend/tests/apps/mentorship/model/mentor_test.py
  • backend/tests/apps/mentorship/model/program_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2026-01-06T21:11:03.537Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:03.537Z
Learning: In backend/tests/apps/mentorship/, prefer pure mock tests (no real database interactions) for model tests. Avoid using Django TestCase with database access; implement tests with mocks/stubs to exercise logic and, when needed, lightweight fixtures without DB. This guideline targets all Python test files under the mentorship module.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_enum_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py
  • backend/tests/apps/mentorship/model/task_level_test.py
  • backend/tests/apps/mentorship/model/mentor_module_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py
  • backend/tests/apps/mentorship/model/start_end_range_test.py
  • backend/tests/apps/mentorship/model/mentee_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py
  • backend/tests/apps/mentorship/model/experience_level_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_module_issues_test.py
  • backend/tests/apps/mentorship/model/task_test.py
  • backend/tests/apps/mentorship/model/module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentee_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py
  • backend/tests/apps/mentorship/model/mentor_test.py
  • backend/tests/apps/mentorship/model/program_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2025-07-13T05:55:46.436Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/program.py:166-166
Timestamp: 2025-07-13T05:55:46.436Z
Learning: In the OWASP Nest mentorship GraphQL mutations, Strawberry GraphQL automatically converts between Django Program instances and ProgramNode types, so mutations can return Program instances directly without manual conversion even when typed as ProgramNode, similar to the Module/ModuleNode pattern.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py
📚 Learning: 2025-07-16T13:49:58.648Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/module.py:28-29
Timestamp: 2025-07-16T13:49:58.648Z
Learning: In the OWASP Nest mentorship system, mentors can be created with only github_user initially (without nest_user) when assigned to modules. This allows mentors to be assigned before they've signed into the system. When these users eventually sign in, the nest_user is automatically linked to the existing github_user, creating a complete mentor profile. This design provides flexibility in mentor assignment workflows.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_module_issues_test.py
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 2000
File: backend/apps/mentorship/management/commands/sync_module_issues.py:109-145
Timestamp: 2025-08-17T11:55:55.990Z
Learning: In the OWASP/Nest mentorship system, tasks are designed to be assigned to only one assignee per issue, even if GitHub issues can have multiple assignees. The sync_module_issues command correctly uses issue.assignees.first() to create one task per issue for the first assignee only.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/mentorship_sync_module_issues_test.py
📚 Learning: 2025-09-29T06:02:35.566Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2178
File: frontend/src/components/SingleModuleCard.tsx:54-54
Timestamp: 2025-09-29T06:02:35.566Z
Learning: In the Module type from types/mentorship.ts, the experienceLevel field is required (experienceLevel: ExperienceLevelEnum), not optional, so null/undefined checks are not needed when accessing this field.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentee_test.py
📚 Learning: 2025-08-31T13:47:15.861Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2155
File: frontend/src/server/queries/programsQueries.ts:81-81
Timestamp: 2025-08-31T13:47:15.861Z
Learning: In frontend/src/server/queries/programsQueries.ts, GET_PROGRAM_DETAILS is actively used in frontend/src/app/my/mentorship/programs/[programKey]/edit/page.tsx for program editing functionality and cannot be removed. It serves a different purpose than GET_PROGRAM_ADMIN_DETAILS, providing comprehensive program information needed for editing.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
📚 Learning: 2025-08-31T13:47:15.861Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2155
File: frontend/src/server/queries/programsQueries.ts:81-81
Timestamp: 2025-08-31T13:47:15.861Z
Learning: In frontend/src/server/queries/programsQueries.ts, GET_PROGRAM_DETAILS and GET_PROGRAM_ADMIN_DETAILS are two separate queries serving different purposes: GET_PROGRAM_DETAILS fetches comprehensive program information while GET_PROGRAM_ADMIN_DETAILS fetches only admin-related details. These queries cannot be removed or merged as they serve different use cases in the application.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
📚 Learning: 2025-08-31T13:47:15.861Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2155
File: frontend/src/server/queries/programsQueries.ts:81-81
Timestamp: 2025-08-31T13:47:15.861Z
Learning: In frontend/src/server/queries/programsQueries.ts, GET_PROGRAM_DETAILS and GET_PROGRAM_ADMIN_DETAILS are two separate queries serving different purposes: GET_PROGRAM_DETAILS fetches comprehensive program information while GET_PROGRAM_ADMIN_DETAILS fetches only admin-related details.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
🧬 Code graph analysis (18)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_enum_test.py (3)
backend/apps/mentorship/api/internal/nodes/enum.py (2)
  • ExperienceLevelEnum (12-18)
  • ProgramStatusEnum (22-27)
backend/apps/mentorship/models/common/experience_level.py (2)
  • ExperienceLevel (6-25)
  • ExperienceLevelChoices (12-18)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py (2)
backend/apps/mentorship/api/internal/nodes/enum.py (2)
  • ExperienceLevelEnum (12-18)
  • ProgramStatusEnum (22-27)
backend/apps/mentorship/api/internal/nodes/mentor.py (1)
  • name (18-20)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py (1)
backend/apps/mentorship/api/internal/nodes/mentor.py (3)
  • avatar_url (13-15)
  • name (18-20)
  • login (23-25)
backend/tests/apps/mentorship/model/task_level_test.py (1)
backend/apps/mentorship/models/task_level.py (1)
  • TaskLevel (8-61)
backend/tests/apps/mentorship/model/mentor_module_test.py (2)
backend/apps/mentorship/models/mentor.py (1)
  • Mentor (11-40)
backend/apps/mentorship/models/mentor_module.py (1)
  • MentorModule (11-42)
backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py (3)
backend/tests/apps/mentorship/management/commands/mentorship_sync_module_issues_test.py (2)
  • command (23-31)
  • make_qs (13-19)
backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py (4)
  • command (39-46)
  • make_qs (11-20)
  • mock_issue (61-69)
  • mock_module (50-57)
backend/tests/apps/owasp/management/commands/owasp_aggregate_contributions_test.py (3)
  • select_related (36-38)
  • order_by (32-34)
  • prefetch_related (40-42)
backend/tests/apps/mentorship/model/start_end_range_test.py (1)
backend/apps/mentorship/models/common/start_end_range.py (1)
  • StartEndRange (6-13)
backend/tests/apps/mentorship/model/mentee_test.py (1)
backend/apps/mentorship/models/mentee.py (1)
  • Mentee (11-49)
backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py (1)
backend/apps/mentorship/api/internal/queries/module.py (4)
  • ModuleQuery (15-50)
  • get_program_modules (19-26)
  • get_project_modules (29-36)
  • get_module (39-50)
backend/tests/apps/mentorship/model/experience_level_test.py (1)
backend/apps/mentorship/models/common/experience_level.py (2)
  • ExperienceLevel (6-25)
  • ExperienceLevelChoices (12-18)
backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py (1)
backend/apps/mentorship/management/commands/mentorship_update_comments.py (3)
  • process_mentorship_modules (31-55)
  • process_module (57-89)
  • process_issue_interests (91-160)
backend/tests/apps/mentorship/model/task_test.py (1)
backend/apps/mentorship/models/task.py (1)
  • Task (10-75)
backend/tests/apps/mentorship/model/module_test.py (2)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/tests/apps/mentorship/model/program_test.py (1)
  • test_str_returns_name (10-12)
backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py (1)
backend/apps/mentorship/api/internal/queries/mentorship.py (3)
  • is_mentor (36-48)
  • get_mentee_details (51-82)
  • get_mentee_module_issues (85-124)
backend/tests/apps/mentorship/model/mentor_test.py (2)
backend/apps/mentorship/models/mentor.py (1)
  • Mentor (11-40)
backend/tests/apps/mentorship/model/mentee_test.py (1)
  • test_str_returns_github_login (8-14)
backend/tests/apps/mentorship/model/program_test.py (1)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py (1)
backend/apps/mentorship/api/internal/queries/program.py (2)
  • get_program (23-32)
  • my_programs (35-80)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (6)
backend/apps/mentorship/api/internal/nodes/enum.py (1)
  • ExperienceLevelEnum (12-18)
backend/apps/mentorship/api/internal/nodes/mentor.py (2)
  • name (18-20)
  • login (23-25)
backend/apps/github/api/internal/nodes/issue.py (1)
  • labels (50-52)
backend/apps/mentorship/models/task.py (1)
  • Task (10-75)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-36)
backend/tests/apps/github/models/issue_test.py (1)
  • issue (26-28)
🔇 Additional comments (46)
backend/tests/apps/mentorship/model/task_level_test.py (2)

1-3: LGTM! Clean imports for pure mock tests.

The imports are minimal and appropriate. Using MagicMock for pure unit tests aligns with the project's preference for mock-based model tests without database interactions.

Based on learnings, pure mock tests are preferred for the mentorship module.


6-15: LGTM! Effective pure mock test.

The test correctly verifies the __str__ method by calling the real implementation with mocked dependencies. Using TaskLevel.__str__(task_level) with a MagicMock is the appropriate pattern to test the actual method logic while mocking self.

backend/tests/apps/mentorship/model/task_test.py (2)

1-6: LGTM!

The imports are clean and follow the learned preference for pure mock tests using MagicMock. Based on learnings, the approach aligns well with the maintainer's preference for mock-based tests in the mentorship module.


34-38: LGTM!

The status constants test correctly verifies all Task.Status enum values and matches the model definition.

backend/tests/apps/mentorship/model/mentor_test.py (1)

1-14: LGTM! Consistent pure mock pattern.

The test correctly verifies that Mentor.__str__ returns the GitHub login using pure mocks without database interactions, which aligns with the preferred testing approach for this module.

backend/tests/apps/mentorship/model/mentee_test.py (1)

1-14: LGTM! Consistent with the mentor test pattern.

The test correctly verifies that Mentee.__str__ returns the GitHub login using the same pure mock approach as the corresponding mentor test.

backend/tests/apps/mentorship/model/mentor_module_test.py (1)

1-18: LGTM! Correctly tests composite string representation.

The test properly mocks the dependencies' __str__ methods and verifies that MentorModule.__str__ returns the expected formatted string.

backend/tests/apps/mentorship/model/module_test.py (1)

25-30: LGTM! Simple and clear.

The test correctly verifies that Module.__str__ returns the module name.

backend/tests/apps/mentorship/management/commands/mentorship_sync_module_issues_test.py (6)

13-19: Well-designed test helper.

The make_qs helper cleanly abstracts QuerySet mocking and improves test readability.


22-31: Solid fixture design.

The fixture provides a well-configured Command instance with mocked I/O streams, enabling focused unit testing without side effects.


40-52: Excellent parametrized test coverage.

The test cases comprehensively cover valid GitHub URLs, malformed inputs, non-GitHub domains, and None values.


65-99: Thorough testing of date retrieval logic.

This test effectively covers the happy path (finding the latest assignment), the not-found case, and exception handling with proper stderr output verification.


102-127: Well-structured test for map building.

The test verifies both the correctness of the case-insensitive label grouping and the user-facing stdout message. Good coverage.


217-248: Good edge case coverage.

The test properly validates the no-match scenario, ensuring that empty results are handled correctly without creating spurious tasks.

backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py (4)

27-34: LGTM!

The early exit test correctly mocks an empty TaskLevel queryset and verifies the warning message is written to stdout.


78-93: Test structure is comprehensive (pending case sensitivity verification).

The test thoroughly covers multiple update scenarios:

  • Setting a level on an issue with none
  • Updating an existing level to a different one
  • Clearing level when no label matches
  • Skipping already-correct issues

The assertions correctly verify the bulk_update call and user-facing message. However, this approval is conditional on resolving the case sensitivity issue flagged above.


96-121: LGTM with case sensitivity caveat.

The test correctly verifies that bulk_update is skipped when all issues already have the correct levels, and the appropriate "up-to-date" message is displayed. However, the same case sensitivity issue noted earlier (Line 107: "Label-A" vs. Line 103: "label-a") applies here as well.


45-56: Case sensitivity concern is not applicable — label matching is case-insensitive.

The command implementation normalizes both TaskLevel labels and Label object names using normalize_name() before comparison (lines 36 and 78 in mentorship_sync_issue_levels.py). The normalize_name() function applies casefold() to convert strings to lowercase, ensuring case-insensitive matching.

The test setup intentionally uses different cases ("label-a" vs "Label-A") to verify this case-insensitive behavior works correctly. This is appropriate test design.

backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py (13)

1-9: LGTM! Clean test imports.

The imports are well-organized and include all necessary dependencies for comprehensive mocking-based tests.


23-28: LGTM! Clean helper function.

The make_user helper provides a clear, concise way to create user mocks for testing.


30-36: LGTM! Well-designed helper.

The make_comment helper with optional created_at parameter provides good flexibility for test scenarios.


38-46: LGTM! Proper command fixture setup.

The fixture correctly mocks stdout and style for testing Django management commands. This aligns with Django conventions for testable command implementations.


49-57: LGTM! Appropriate ORM mock chaining.

The fixture correctly mocks Django's queryset chaining pattern for module.project.repositories.filter().values_list().distinct(), providing realistic behavior for tests.


60-69: LGTM! Clean baseline issue fixture.

The fixture provides a sensible default mock issue with empty comments, allowing individual tests to override comment behavior as needed.


72-78: LGTM! Clear early-exit test.

The test correctly verifies that the command exits gracefully with an appropriate message when no published modules exist.


80-90: LGTM! Thorough edge-case coverage.

The test correctly handles the scenario where published modules exist but none have labels, verifying the appropriate warning message is displayed.


128-156: LGTM! Comprehensive interest registration test.

The test thoroughly verifies new interest registration, including proper bulk_create usage, correct object attributes, and appropriate success messaging.


158-187: LGTM! Proper interest removal verification.

The test correctly verifies that interests are removed when a user's comment no longer matches the interest pattern, including proper delete operation and warning message.


220-248: LGTM! Important aggregation test.

This test correctly verifies that multiple comments from the same user are aggregated properly, ensuring only one interest is created even when a user posts multiple comments. This is crucial for preventing duplicate interest records.


250-300: LGTM! Excellent comprehensive scenario test.

This test provides outstanding coverage of the most complex scenario: multiple users with mixed interest changes (some created, some removed) in a single processing run. The assertions thoroughly verify both the database operations and the corresponding user feedback messages.


1-300: Excellent test coverage with proper mocking patterns.

This test suite demonstrates strong engineering practices:

  • Pure mock-based tests without database dependencies (as per learnings from your previous feedback)
  • Proper use of stdout.write for Django management command output
  • Comprehensive edge-case coverage including early exits, aggregation, and multi-user scenarios
  • Clean helper functions and fixtures for test setup

The tests effectively validate the command's orchestration, interest tracking logic, and user feedback messaging.

Based on learnings, the pure mock approach without Django TestCase aligns with the mentorship test suite preferences.

backend/tests/apps/mentorship/model/experience_level_test.py (1)

4-9: LGTM!

The test correctly validates that ExperienceLevelChoices constants map to their expected string values. This is a clean, focused unit test with no database dependencies.

backend/tests/apps/mentorship/model/program_test.py (1)

5-8: LGTM!

The test correctly validates that ProgramStatus constants map to their expected string values.

backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py (2)

10-33: LGTM!

The fixtures are well-structured and follow best practices:

  • Proper use of MagicMock for the GitHub user
  • Coverage of both scenarios (with and without github_user)
  • Clear, descriptive fixture names and docstrings

36-68: LGTM!

Excellent test coverage for MentorNode:

  • Tests validate all field resolvers (avatar_url, name, login)
  • Edge cases handled properly (returns empty string when github_user is None)
  • Tests align with the actual resolver implementations
backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py (5)

17-37: LGTM!

The FakeProgramNode class is a reasonable approach for testing without database dependencies. It mimics ProgramNode's structure and behavior while maintaining isolation.


40-50: LGTM!

The fixture properly initializes the fake node with mocked admins.


53-76: LGTM!

The tests comprehensively validate all ProgramNode fields and the admins resolver.


78-85: LGTM!

The test correctly validates the PaginatedPrograms structure.


88-140: LGTM!

The input type tests validate both type annotations and default values. While testing __annotations__ directly is slightly fragile, it's an effective way to verify Strawberry Input type structures. The tests for default values (e.g., domains=[], tags=[]) are particularly valuable.

backend/tests/apps/mentorship/api/internal/nodes/api_internal_enum_test.py (1)

8-23: LGTM!

These tests are critical for validating that the API enum layer correctly maps to the model choice layer. They ensure the contract between the GraphQL API and the database remains consistent.

backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentee_test.py (1)

1-58: LGTM! Well-structured mentee node tests.

The tests follow the mock-based testing pattern and provide good coverage of the MenteeNode functionality:

  • Field initialization and access
  • Resolver methods for avatar_url and experience_level
  • Edge case handling for None experience_level with appropriate default

The use of # type: ignore[assignment] on line 54 is appropriate for testing the None case.

Based on learnings, these tests align with the preferred pure mock approach for the mentorship test suite.

backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (1)

143-390: Good mock-based test coverage.

The tests properly mock all Django ORM interactions and cover various scenarios including success paths, empty results, and filtering. The use of patches ensures no database access occurs during test execution.

Based on learnings, these tests align with the preferred pure mock approach for the mentorship test suite.

backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py (1)

1-114: LGTM! Comprehensive module query tests.

The tests provide excellent coverage of the ModuleQuery class:

  • Both program and project module retrieval (success + empty cases)
  • Single module retrieval with proper exception handling
  • All Django ORM interactions are properly mocked with patches
  • Assertions verify both return values and method call signatures

Based on learnings, these tests follow the preferred pure mock approach without database interactions.

backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py (1)

1-237: LGTM! Thorough program query tests.

Excellent test coverage for ProgramQuery:

  • get_program: success and DoesNotExist paths
  • my_programs: comprehensive scenarios including admin/mentor roles, empty results, search filtering, and pagination
  • All Django ORM calls properly mocked with patches
  • Mock setup correctly handles complex relationships and user role assignment

The tests follow best practices with clear test names and proper assertion of both results and method calls.

Based on learnings, these tests follow the preferred pure mock approach for the mentorship module.

backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py (1)

1-467: LGTM! Comprehensive mentorship query tests.

Excellent coverage across three query types:

  • is_mentor: validates various edge cases including empty login, missing GitHub user, and mentor status
  • get_mentee_details: covers success path and all exception scenarios (module, user, mentee not found, not enrolled)
  • get_mentee_module_issues: includes success, error cases, and pagination scenarios

All Django ORM interactions are properly mocked. The use of pytest.mark.parametrize on line 76 is a good practice for testing multiple edge cases efficiently.

Based on learnings, these tests follow the preferred pure mock approach for the mentorship test suite.

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 (3)
backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py (3)

11-20: Consider using a callable for __iter__ to avoid iterator exhaustion.

If a test iterates over the mock queryset multiple times, the current implementation will return an exhausted iterator on subsequent iterations because iter(items) is evaluated once when setting return_value.

This is low-risk since each test creates fresh mocks, but using side_effect would be more robust.

♻️ Suggested fix
 def make_qs(iterable, exist):
     """Return a queryset-like MagicMock that is iterable."""
     qs = MagicMock(name="QuerySet")
     items = list(iterable)
     qs.exists.return_value = exist
-    qs.__iter__.return_value = iter(items)
+    qs.__iter__ = lambda self: iter(items)
     qs.all.return_value = items
     qs.count.return_value = len(items)
     qs.distinct.return_value = qs
     return qs

72-89: Parameter name mock_module shadows the fixture.

The @patch decorator injects the mocked Module class as mock_module, which shares the same name as the mock_module fixture. While the fixture isn't used in these tests, this naming can confuse maintainers.

Consider renaming the patch parameter to mock_module_class or patched_module for clarity.


92-125: Confusing parameter naming: mock_issue_1 is the patched Issue class.

The parameter mock_issue_1 (from @patch...Issue) is the patched Issue class, while mock_issue is the fixture representing an issue instance. This naming is misleading—consider renaming to mock_issue_class or patched_issue for the patch parameter.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 907ac29 and 5316b0d.

📒 Files selected for processing (8)
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_enum_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentee_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_enum_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentee_test.py
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:08.745Z
Learning: In the mentorship test suite (backend/tests/apps/mentorship/), the maintainer kart-u prefers pure mock tests over Django TestCase with database interactions for model tests.
📚 Learning: 2026-01-06T21:11:03.537Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:03.537Z
Learning: In backend/tests/apps/mentorship/, prefer pure mock tests (no real database interactions) for model tests. Avoid using Django TestCase with database access; implement tests with mocks/stubs to exercise logic and, when needed, lightweight fixtures without DB. This guideline targets all Python test files under the mentorship module.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
🧬 Code graph analysis (2)
backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py (1)
backend/apps/mentorship/api/internal/queries/mentorship.py (3)
  • is_mentor (36-48)
  • get_mentee_details (51-82)
  • get_mentee_module_issues (85-124)
backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py (1)
backend/apps/mentorship/management/commands/mentorship_update_comments.py (3)
  • process_mentorship_modules (31-55)
  • process_module (57-89)
  • process_issue_interests (91-160)
🔇 Additional comments (4)
backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py (1)

128-300: LGTM!

The process_issue_interests tests comprehensively cover:

  • New interest creation with bulk_create
  • Interest removal with delete
  • Multiple comments from a single user (aggregation)
  • Mixed scenarios with multiple users

The assertions correctly verify both ORM interactions and stdout output messages. The pure mock approach aligns with the project's test guidelines. Based on learnings, this approach is preferred for the mentorship test suite.

backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py (3)

18-81: LGTM!

The TestIsMentor class provides good coverage:

  • Tests the happy path when user is a mentor
  • Tests when user exists but is not a mentor
  • Tests when GitHub user doesn't exist
  • Parametrized tests for empty/whitespace login values

The assertions correctly verify both the return values and the expected ORM calls.


83-220: LGTM!

The TestGetMenteeDetails class thoroughly covers all code paths:

  • Successful retrieval with correct field assertions
  • Module.DoesNotExist exception handling
  • GithubUser.DoesNotExist exception handling
  • Mentee.DoesNotExist exception handling
  • Not-enrolled scenario with correct error message

The mock setup and assertions correctly mirror the production code's only() field selections.


288-466: LGTM!

The remaining tests in TestGetMenteeModuleIssues provide good coverage:

  • Error scenarios for missing module, GitHub user, and mentee
  • Not-enrolled scenario with correct error message matching
  • Pagination with custom limit and offset parameters
  • Edge case for pagination beyond available results

@kart-u kart-u force-pushed the test_mentorship_backend branch from 5316b0d to 359147c Compare January 8, 2026 08:08
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

🤖 Fix all issues with AI agents
In @backend/tests/apps/mentorship/model/program_test.py:
- Around line 10-12: Replace the direct instantiation of the Django model in
test_str_returns_name with a pure mock: instead of Program(name="Security
Program"), create a MagicMock named mock_program_instance, set its name
attribute to "Security Program", and call Program.__str__(mock_program_instance)
to assert the returned string; this keeps the test consistent with other
mentorship model tests and avoids instantiating the real Program model.
🧹 Nitpick comments (7)
backend/tests/apps/mentorship/model/mentor_module_test.py (1)

8-18: Consider adding a docstring for test clarity.

While the test is functionally correct, adding a docstring would improve readability and make the test's intent explicit, especially given the unconventional pattern of calling the unbound __str__ method on a mocked instance.

📝 Proposed enhancement
 class TestMentorModule:
     def test_str_returns_readable_identifier(self):
+        """Test that MentorModule.__str__ returns 'mentor of module' format."""
         mentor = MagicMock(spec=Mentor)
         mentor.__str__.return_value = "test_mentor"
backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py (1)

18-24: Consider consolidating the make_qs helper.

The make_qs helper is duplicated across multiple test files (mentorship_update_comments_test.py, mentorship_sync_module_issues_test.py) with slight variations. The version in mentorship_update_comments_test.py includes a count() method that this one lacks.

♻️ Refactor suggestion: Extract to shared test utility

Consider extracting make_qs to a shared test utility module (e.g., backend/tests/apps/mentorship/utils.py or backend/tests/apps/mentorship/conftest.py) to eliminate duplication and ensure consistency:

def make_qs(iterable, exist):
    """Return a queryset-like MagicMock that is iterable."""
    qs = MagicMock(name="QuerySet")
    items = list(iterable)
    qs.exists.return_value = exist
    qs.__iter__.return_value = iter(items)
    qs.all.return_value = items
    qs.count.return_value = len(items)
    qs.distinct.return_value = qs
    return qs

This would standardize the helper and include all necessary methods.

backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py (1)

11-20: Consider consolidating the make_qs helper.

This helper is duplicated across multiple test files (mentorship_sync_issue_levels_test.py, mentorship_sync_module_issues_test.py) with slight variations. This version is more complete than others (includes count() and distinct() methods).

♻️ Refactor suggestion: Extract to shared test utility

Consider extracting make_qs to a shared test utility module (e.g., backend/tests/apps/mentorship/utils.py or backend/tests/apps/mentorship/conftest.py) to eliminate duplication and ensure consistency across all mentorship tests. This version appears to be the most complete and should serve as the canonical implementation.

# In backend/tests/apps/mentorship/conftest.py or utils.py
def make_qs(iterable, exist):
    """Return a queryset-like MagicMock that is iterable."""
    qs = MagicMock(name="QuerySet")
    items = list(iterable)
    qs.exists.return_value = exist
    qs.__iter__.return_value = iter(items)
    qs.all.return_value = items
    qs.count.return_value = len(items)
    qs.distinct.return_value = qs
    return qs
backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py (1)

17-50: Consider using real ProgramNode instead of FakeProgramNode.

The current approach tests a FakeProgramNode class rather than the actual ProgramNode implementation. This means the tests won't catch regressions if the real ProgramNode structure or behavior changes.

Compare with api_internal_mentor_test.py (lines 21-22), which instantiates the real MentorNode class and mocks only its dependencies. A similar approach here would:

  • Instantiate real ProgramNode instances
  • Mock the underlying model/queryset that admins() depends on
  • Exercise actual resolver logic while maintaining pure mock tests

This ensures the tests validate production code behavior rather than a test double.

Based on learnings, prefer pure mock tests without database interactions.

♻️ Example refactor approach
@pytest.fixture
def mock_program_node():
    """Fixture for a mock ProgramNode instance with mocked dependencies."""
    # Create a mock Django Program model instance
    mock_program_model = MagicMock()
    mock_program_model.id = 1
    mock_program_model.key = "test-program"
    mock_program_model.name = "Test Program"
    # ... set other fields
    
    # Mock the admins queryset
    mock_program_model.admins.all.return_value = [
        MagicMock(name="admin1"),
        MagicMock(name="admin2"),
    ]
    
    # Instantiate real ProgramNode with the mocked model
    program_node = ProgramNode(id=strawberry.ID("prog-1"))
    # Set the underlying model or mock the data source ProgramNode uses
    program_node._mock_model = mock_program_model  # Adjust based on actual implementation
    
    return program_node
backend/tests/apps/mentorship/model/task_level_test.py (1)

1-26: LGTM! Well-structured mock tests for TaskLevel.

The tests correctly validate that TaskLevel.__str__ accesses module.name directly rather than calling str(module). The use of pure mocks aligns with the project's testing preferences.

♻️ Optional: Consider more idiomatic __str__ invocation

While calling TaskLevel.__str__(task_level) as a class method works correctly, the more Pythonic approach is to use the str() built-in:

-        assert TaskLevel.__str__(task_level) == "Test Module - Beginner Task"
+        assert str(task_level) == "Test Module - Beginner Task"

Apply similarly to line 26. This is purely stylistic and doesn't affect functionality.

backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py (2)

35-47: Remove unused mock_info fixture parameter.

The get_program method doesn't accept an info parameter (per the implementation in program.py), so mock_info is injected but never used. This applies to both test_get_program_success and test_get_program_does_not_exist.

♻️ Proposed fix
     @patch("apps.mentorship.api.internal.queries.program.Program.objects.prefetch_related")
     def test_get_program_success(
-        self, mock_program_prefetch_related: MagicMock, mock_info: MagicMock, api_program_queries
+        self, mock_program_prefetch_related: MagicMock, api_program_queries
     ) -> None:
     @patch("apps.mentorship.api.internal.queries.program.Program.objects.prefetch_related")
     def test_get_program_does_not_exist(
-        self, mock_program_prefetch_related: MagicMock, mock_info: MagicMock, api_program_queries
+        self, mock_program_prefetch_related: MagicMock, api_program_queries
     ) -> None:

81-94: Remove dead mock setup for modules relationship.

Lines 83, 87 set up mock_program.modules.return_value.mentors.return_value... but this doesn't match actual code usage. The my_programs method only accesses program.admins.all() for role checking—the modules__mentors__github_user path is used only in prefetch_related for the filter query, not for direct attribute access. This dead setup adds confusion.

Same issue appears in lines 176, 211, 215, 219.

♻️ Proposed fix for lines 81-87
         mock_admin_program = MagicMock(spec=Program, nest_created_at="2023-01-01", id=1)
         mock_admin_program.admins.all.return_value = [mock_mentor]
-        mock_admin_program.modules.return_value.mentors.return_value.github_user.return_value = []

         mock_mentor_program = MagicMock(spec=Program, nest_created_at="2023-01-02", id=2)
         mock_mentor_program.admins.all.return_value = []
-        mock_mentor_program.modules.return_value.mentors.return_value = [mock_mentor]
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5316b0d and 359147c.

📒 Files selected for processing (29)
  • backend/apps/github/models/__init__.py
  • backend/pyproject.toml
  • backend/tests/apps/mentorship/__init__.py
  • backend/tests/apps/mentorship/api/__init__.py
  • backend/tests/apps/mentorship/api/internal/__init__.py
  • backend/tests/apps/mentorship/api/internal/nodes/__init__.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_enum_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentee_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py
  • backend/tests/apps/mentorship/api/internal/queries/__init__.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
  • backend/tests/apps/mentorship/management/__init__.py
  • backend/tests/apps/mentorship/management/commands/__init__.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_module_issues_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
  • backend/tests/apps/mentorship/model/__init__.py
  • backend/tests/apps/mentorship/model/experience_level_test.py
  • backend/tests/apps/mentorship/model/mentee_test.py
  • backend/tests/apps/mentorship/model/mentor_module_test.py
  • backend/tests/apps/mentorship/model/mentor_test.py
  • backend/tests/apps/mentorship/model/module_test.py
  • backend/tests/apps/mentorship/model/program_test.py
  • backend/tests/apps/mentorship/model/task_level_test.py
  • backend/tests/apps/mentorship/model/task_test.py
💤 Files with no reviewable changes (1)
  • backend/pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (8)
  • backend/tests/apps/mentorship/model/mentor_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_module_issues_test.py
  • backend/tests/apps/mentorship/model/module_test.py
  • backend/tests/apps/mentorship/model/mentee_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_enum_test.py
  • backend/tests/apps/mentorship/model/task_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentee_test.py
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:08.745Z
Learning: In the mentorship test suite (backend/tests/apps/mentorship/), the maintainer kart-u prefers pure mock tests over Django TestCase with database interactions for model tests.
📚 Learning: 2026-01-06T21:11:03.537Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:03.537Z
Learning: In backend/tests/apps/mentorship/, prefer pure mock tests (no real database interactions) for model tests. Avoid using Django TestCase with database access; implement tests with mocks/stubs to exercise logic and, when needed, lightweight fixtures without DB. This guideline targets all Python test files under the mentorship module.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py
  • backend/tests/apps/mentorship/model/experience_level_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
  • backend/tests/apps/mentorship/model/mentor_module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py
  • backend/tests/apps/mentorship/model/task_level_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py
  • backend/tests/apps/mentorship/model/program_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py
  • backend/tests/apps/mentorship/model/experience_level_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
  • backend/tests/apps/mentorship/model/mentor_module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py
  • backend/tests/apps/mentorship/model/task_level_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py
  • backend/tests/apps/mentorship/model/program_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
  • backend/apps/github/models/__init__.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py
  • backend/tests/apps/mentorship/model/experience_level_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
  • backend/tests/apps/mentorship/model/mentor_module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py
  • backend/tests/apps/mentorship/model/task_level_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py
  • backend/tests/apps/mentorship/model/program_test.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
  • backend/apps/github/models/__init__.py
  • backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py
📚 Learning: 2025-07-13T05:55:46.436Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/program.py:166-166
Timestamp: 2025-07-13T05:55:46.436Z
Learning: In the OWASP Nest mentorship GraphQL mutations, Strawberry GraphQL automatically converts between Django Program instances and ProgramNode types, so mutations can return Program instances directly without manual conversion even when typed as ProgramNode, similar to the Module/ModuleNode pattern.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py
  • backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py
📚 Learning: 2025-08-31T13:47:15.861Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2155
File: frontend/src/server/queries/programsQueries.ts:81-81
Timestamp: 2025-08-31T13:47:15.861Z
Learning: In frontend/src/server/queries/programsQueries.ts, GET_PROGRAM_DETAILS is actively used in frontend/src/app/my/mentorship/programs/[programKey]/edit/page.tsx for program editing functionality and cannot be removed. It serves a different purpose than GET_PROGRAM_ADMIN_DETAILS, providing comprehensive program information needed for editing.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
📚 Learning: 2025-08-31T13:47:15.861Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2155
File: frontend/src/server/queries/programsQueries.ts:81-81
Timestamp: 2025-08-31T13:47:15.861Z
Learning: In frontend/src/server/queries/programsQueries.ts, GET_PROGRAM_DETAILS and GET_PROGRAM_ADMIN_DETAILS are two separate queries serving different purposes: GET_PROGRAM_DETAILS fetches comprehensive program information while GET_PROGRAM_ADMIN_DETAILS fetches only admin-related details. These queries cannot be removed or merged as they serve different use cases in the application.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
📚 Learning: 2025-08-31T13:47:15.861Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2155
File: frontend/src/server/queries/programsQueries.ts:81-81
Timestamp: 2025-08-31T13:47:15.861Z
Learning: In frontend/src/server/queries/programsQueries.ts, GET_PROGRAM_DETAILS and GET_PROGRAM_ADMIN_DETAILS are two separate queries serving different purposes: GET_PROGRAM_DETAILS fetches comprehensive program information while GET_PROGRAM_ADMIN_DETAILS fetches only admin-related details.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py
🧬 Code graph analysis (10)
backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py (1)
backend/apps/mentorship/api/internal/queries/module.py (4)
  • ModuleQuery (15-50)
  • get_program_modules (19-26)
  • get_project_modules (29-36)
  • get_module (39-50)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py (1)
backend/apps/mentorship/api/internal/nodes/enum.py (2)
  • ExperienceLevelEnum (12-18)
  • ProgramStatusEnum (22-27)
backend/tests/apps/mentorship/model/experience_level_test.py (1)
backend/apps/mentorship/models/common/experience_level.py (2)
  • ExperienceLevel (6-25)
  • ExperienceLevelChoices (12-18)
backend/tests/apps/mentorship/model/mentor_module_test.py (2)
backend/apps/mentorship/models/mentor.py (1)
  • Mentor (11-40)
backend/apps/mentorship/models/mentor_module.py (1)
  • MentorModule (11-42)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_mentor_test.py (1)
backend/apps/mentorship/api/internal/nodes/mentor.py (3)
  • avatar_url (13-15)
  • name (18-20)
  • login (23-25)
backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py (2)
backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py (2)
  • command (39-46)
  • make_qs (11-20)
backend/tests/apps/mentorship/management/commands/mentorship_sync_module_issues_test.py (2)
  • command (23-31)
  • make_qs (13-19)
backend/tests/apps/mentorship/model/program_test.py (2)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/tests/apps/mentorship/model/module_test.py (1)
  • test_str_returns_name (25-30)
backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py (1)
backend/apps/mentorship/api/internal/queries/program.py (2)
  • get_program (23-32)
  • my_programs (35-80)
backend/apps/github/models/__init__.py (2)
backend/tests/apps/github/models/issue_test.py (1)
  • issue (26-28)
backend/apps/github/models/issue.py (1)
  • Issue (19-235)
backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py (1)
backend/apps/mentorship/api/internal/queries/mentorship.py (4)
  • MentorshipQuery (32-124)
  • is_mentor (36-48)
  • get_mentee_details (51-82)
  • get_mentee_module_issues (85-124)
🔇 Additional comments (20)
backend/tests/apps/mentorship/model/mentor_module_test.py (2)

1-3: LGTM! Imports are appropriate for mock-based testing.

The imports correctly bring in MagicMock for creating test doubles and the necessary models for testing the __str__ method.


6-18: Test logic is correct and follows codebase patterns.

The test properly verifies that MentorModule.__str__ composes the mentor and module string representations. The use of pure mocks aligns with the project's testing approach for mentorship models.

Based on learnings, pure mock tests are preferred for this module.

backend/apps/github/models/__init__.py (1)

5-5: LGTM! Issue model is now publicly accessible.

The re-export of Issue aligns with the existing pattern for other models (Comment, Commit, Label, etc.) and makes the model accessible to tests and other modules that import from apps.github.models.

backend/tests/apps/mentorship/management/commands/mentorship_sync_issue_levels_test.py (5)

1-5: LGTM! Standard test imports.

The imports are appropriate for a Django management command test using pytest and mocking.


8-15: LGTM! Command fixture follows Django testing conventions.

The fixture properly mocks stdout and style methods, aligning with Django management command testing patterns. Based on learnings, this approach avoids database interactions and uses pure mocks.


27-34: LGTM! Early exit test provides good coverage.

The test correctly verifies that the command exits early with an appropriate message when no TaskLevel objects exist.


37-93: LGTM! Comprehensive test coverage for the update logic.

This test thoroughly covers the main update scenarios:

  • New level assignment (issue_with_label_a)
  • Level update (issue_with_label_b)
  • Level removal when no match (issue_no_match)
  • No change when already correct (issue_already_up_to_date)

The assertions correctly verify bulk_update is called with only the modified issues and the success message is output.


96-121: LGTM! No-update scenario is properly tested.

The test correctly verifies that bulk_update is not called when all issues already have the correct level, and the appropriate up-to-date message is displayed.

backend/tests/apps/mentorship/management/commands/mentorship_update_comments_test.py (6)

1-9: LGTM! Imports are appropriate.

The imports include all necessary modules for testing the management command with mocks and pytest.


23-35: LGTM! Helper factories provide clean test data construction.

The make_user and make_comment helpers create focused, readable test fixtures without database interactions, aligning with the maintainer's preference for pure mock tests.


38-69: LGTM! Fixtures are well-structured and reusable.

The command, mock_module, and mock_issue fixtures provide consistent test setup with appropriate mocking of Django ORM behavior and stdout/styling for command output verification.


72-89: LGTM! Early exit conditions are properly tested.

Both tests verify that the command exits gracefully with appropriate messages when preconditions aren't met (no published modules, no modules with labels).


92-125: LGTM! Orchestration test verifies the integration flow.

The test confirms that process_module correctly coordinates the syncing of issue comments and processing of user interests, verifying both operations are called with the expected arguments.


128-300: LGTM! Comprehensive coverage of interest processing scenarios.

The test suite thoroughly covers all scenarios for process_issue_interests:

  • New interest registration (lines 128-155)
  • Interest removal (lines 158-186)
  • Existing interest cleanup (lines 189-217)
  • Multiple comments from single user (lines 220-247)
  • Mixed scenarios with multiple users (lines 250-300)

The tests verify correct bulk operations, filtering, and appropriate stdout messages for each scenario. The assertions in the multi-user test (lines 250-300) are particularly thorough in validating the complex interaction between interest creation and removal.

backend/tests/apps/mentorship/model/program_test.py (1)

5-8: LGTM! Correct enum value assertions.

The enum value checks correctly validate the ProgramStatus choices.

backend/tests/apps/mentorship/api/internal/queries/api_queries_module_test.py (1)

1-112: LGTM! Comprehensive and well-structured query tests.

The test suite thoroughly covers ModuleQuery behaviors:

  • Both success and empty-result scenarios for program/project module retrieval
  • Single module lookup with proper exception handling
  • Correct verification of ORM chain methods (filter, select_related, prefetch_related, order_by)
  • Pure mock approach without database interactions

The tests align well with the project's testing standards.

backend/tests/apps/mentorship/model/experience_level_test.py (1)

1-9: LGTM! Clear and correct enum choice validation.

The test straightforwardly validates the ExperienceLevel choice mappings, ensuring consistency between enum values and their string representations.

backend/tests/apps/mentorship/api/internal/queries/api_queries_mentorship_test.py (1)

1-465: LGTM! Excellent comprehensive test coverage for MentorshipQuery.

This test suite is exceptionally thorough:

TestIsMentor:

  • Validates mentor identification logic
  • Handles edge cases (empty/whitespace/None login) using parametrize
  • Tests GithubUser.DoesNotExist scenario

TestGetMenteeDetails:

  • Covers successful mentee detail retrieval with proper field mapping
  • Tests all failure paths (Module, GithubUser, Mentee DoesNotExist)
  • Validates enrollment checking

TestGetMenteeModuleIssues:

  • Tests issue retrieval with proper ORM chaining (filter, only, prefetch_related, order_by)
  • Validates pagination with limit/offset parameters
  • Covers out-of-range pagination gracefully
  • Tests all failure paths comprehensively

The extensive use of mocks aligns perfectly with the project's preference for pure mock tests without database interactions.

backend/tests/apps/mentorship/api/internal/queries/api_queries_program_test.py (2)

1-29: LGTM!

Imports are appropriate, and fixtures are well-structured with clear docstrings. The mock_info fixture correctly sets up the nested context.request.user.github_user structure matching the actual code's access pattern.


117-235: LGTM!

The remaining tests provide good coverage for:

  • Mentor not found scenario with correct empty response
  • No programs found with proper pagination defaults
  • Search filtering with name__icontains verification
  • Pagination calculation across multiple pages

The test structure is clean and follows the pure mock approach per learnings. Based on learnings, this aligns with the preferred testing style for the mentorship module.

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

🧹 Nitpick comments (4)
backend/tests/apps/mentorship/model/program_test.py (1)

11-14: str test looks correct. Consider adding docstrings.

The test correctly verifies the str method using a mock and follows the pattern from module_test.py. Consider adding docstrings to match the style shown in the relevant code snippets from module_test.py.

📝 Suggested docstrings
     def test_program_status_choices(self):
+        """Unit test: verify ProgramStatus enum values map to expected strings."""
         assert Program.ProgramStatus.DRAFT == "draft"
         assert Program.ProgramStatus.PUBLISHED == "published"
         assert Program.ProgramStatus.COMPLETED == "completed"

     def test_str_returns_name(self):
+        """Unit test: __str__ should return the program name (pure mock)."""
         mock_program_instance = MagicMock(spec=Program)
         mock_program_instance.name = "Security Program"
         assert Program.__str__(mock_program_instance) == "Security Program"

Based on relevant code snippets showing the docstring pattern in module_test.py.

backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (3)

11-151: Consider testing the actual ModuleNode class instead of a duplicate implementation.

The FakeModuleNode class replicates the resolver logic from the actual ModuleNode. This means the tests verify the behavior of FakeModuleNode, not the production code. If the actual ModuleNode implementation changes, these tests may still pass while the production code is broken.

Based on learnings, pure mock tests are preferred - this is achievable by importing the actual ModuleNode class and mocking its ORM dependencies (e.g., _mentors_manager, menteemodule_set, _issues_qs), rather than creating a separate test double with copied logic.

Is there a specific reason the actual ModuleNode cannot be instantiated for testing (e.g., Strawberry type initialization requirements)? If so, consider adding a comment explaining this constraint and perhaps a mechanism to ensure FakeModuleNode stays in sync with the actual implementation.


384-390: Missing docstring for consistency.

This test function lacks a docstring, unlike all other test functions in the file.

📝 Add docstring for consistency
 def test_module_node_task_assigned_at_none(mock_module_node):
+    """Test task_assigned_at when no assignment timestamp is found."""
     with patch("apps.mentorship.models.task.Task.objects") as mock_task_objects:

37-151: Docstrings should use imperative mood per linting rules (D401).

The static analysis flagged multiple docstrings starting with "Retrieves..." or "Returns..." instead of "Retrieve..." or "Return...". If D401 is enforced in CI, these will need adjustment.

Example fix for line 38:

     def mentors(self):
-        """Retrieves all mentors associated with the module."""
+        """Retrieve all mentors associated with the module."""
         return self._mentors_manager.all()

Apply similar pattern to other docstrings (lines 42, 53, 70, 74, 83, 90, 101, 110, 124, 139).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 359147c and 52d4306.

📒 Files selected for processing (2)
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • backend/tests/apps/mentorship/model/program_test.py
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:08.745Z
Learning: In the mentorship test suite (backend/tests/apps/mentorship/), the maintainer kart-u prefers pure mock tests over Django TestCase with database interactions for model tests.
📚 Learning: 2026-01-06T21:11:03.537Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:03.537Z
Learning: In backend/tests/apps/mentorship/, prefer pure mock tests (no real database interactions) for model tests. Avoid using Django TestCase with database access; implement tests with mocks/stubs to exercise logic and, when needed, lightweight fixtures without DB. This guideline targets all Python test files under the mentorship module.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • backend/tests/apps/mentorship/model/program_test.py
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 2000
File: backend/apps/mentorship/management/commands/sync_module_issues.py:109-145
Timestamp: 2025-08-17T11:55:55.990Z
Learning: In the OWASP/Nest mentorship system, tasks are designed to be assigned to only one assignee per issue, even if GitHub issues can have multiple assignees. The sync_module_issues command correctly uses issue.assignees.first() to create one task per issue for the first assignee only.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • backend/tests/apps/mentorship/model/program_test.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • backend/tests/apps/mentorship/model/program_test.py
🧬 Code graph analysis (2)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (3)
backend/apps/mentorship/api/internal/nodes/enum.py (1)
  • ExperienceLevelEnum (12-18)
backend/apps/mentorship/models/task.py (1)
  • Task (10-75)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-36)
backend/tests/apps/mentorship/model/program_test.py (3)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/tests/apps/mentorship/model/module_test.py (1)
  • test_str_returns_name (25-30)
backend/apps/mentorship/api/internal/nodes/mentor.py (1)
  • name (18-20)
🪛 Ruff (0.14.10)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py

38-38: First line of docstring should be in imperative mood: "Retrieves all mentors associated with the module."

(D401)


42-42: First line of docstring should be in imperative mood: "Retrieves all mentees associated with the module."

(D401)


53-53: First line of docstring should be in imperative mood: "Retrieves mentees assigned to a specific issue within the module."

(D401)


70-70: First line of docstring should be in imperative mood: "Returns the name of the associated project."

(D401)


74-74: First line of docstring should be in imperative mood: "Retrieves issues associated with the module, with filtering and pagination."

(D401)


83-83: First line of docstring should be in imperative mood: "Returns the count of issues associated with the module."

(D401)


90-90: First line of docstring should be in imperative mood: "Retrieves a sorted list of unique labels used across issues in the module."

(D401)


101-101: First line of docstring should be in imperative mood: "Retrieves a single issue by its number within the module."

(D401)


110-110: First line of docstring should be in imperative mood: "Retrieves users who have expressed interest in a specific issue within the module."

(D401)


124-124: First line of docstring should be in imperative mood: "Retrieves the deadline for a task associated with a specific issue."

(D401)


139-139: First line of docstring should be in imperative mood: "Retrieves the assignment timestamp for a task associated with a specific issue."

(D401)

🔇 Additional comments (3)
backend/tests/apps/mentorship/model/program_test.py (1)

5-9: Enum value tests look correct.

The test correctly verifies that ProgramStatus enum values map to their expected string representations.

backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (2)

154-171: Well-structured pytest fixture with comprehensive mock setup.

The fixture properly configures return values for the mock managers and querysets. Each test function receives a fresh instance, avoiding state leakage between tests.


393-421: Good approach - testing actual input classes directly.

This test correctly instantiates the actual CreateModuleInput and UpdateModuleInput classes and verifies their default values. This is the preferred pattern compared to testing a fake implementation.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 8, 2026
@kart-u kart-u changed the title Test mentorship backend Added tests for backend/app/mentorship Jan 8, 2026
@kart-u kart-u changed the title Added tests for backend/app/mentorship Improving testing for backend/app/mentorship Jan 8, 2026
@kart-u kart-u changed the title Improving testing for backend/app/mentorship Improved testing for backend/app/mentorship Jan 8, 2026
@kart-u
Copy link
Contributor Author

kart-u commented Jan 8, 2026

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

✅ Actions performed

Full review triggered.

@kart-u kart-u marked this pull request as ready for review January 8, 2026 10:31
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

🤖 Fix all issues with AI agents
In
@backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py:
- Around line 373-379: Add a descriptive docstring to the test function
test_module_node_task_assigned_at_none describing what scenario it verifies
(that task_assigned_at returns None when no task exists for the given
issue_number). Locate the test function named
test_module_node_task_assigned_at_none and insert a one-line docstring directly
under the def line to match the style of other tests in the file.
🧹 Nitpick comments (3)
backend/tests/apps/mentorship/model/program_test.py (1)

7-15: Optional: Consider adding docstrings for consistency.

Other test files in the mentorship suite (e.g., module_test.py) include brief docstrings on test methods. Adding them here would maintain consistency across the test suite.

📝 Example with docstrings
 def test_program_status_choices(self):
+    """Verify ProgramStatus enum values map to expected strings."""
     assert Program.ProgramStatus.DRAFT == "draft"
     assert Program.ProgramStatus.PUBLISHED == "published"
     assert Program.ProgramStatus.COMPLETED == "completed"

 def test_str_returns_name(self):
+    """Unit test: __str__ should return the program name (pure mock)."""
     mock_program_instance = MagicMock(spec=Program)
     mock_program_instance.name = "Security Program"
     assert Program.__str__(mock_program_instance) == "Security Program"
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (2)

143-160: Consider more explicit mock setup for maintainability.

The fixture uses chained method mocks (lines 149-155, 157-158) which can be fragile if the implementation's ORM call order changes. Additionally, the variable mocked_query_prefetch is reused on lines 154 and 157, reducing clarity.

Consider using more explicit mock configuration with named intermediates or Mock(spec=...) to make the test setup more resilient to implementation changes.

💡 Example refactor
@pytest.fixture
def mock_module_node():
    """Fixture for a mock ModuleNode instance."""
    m = FakeModuleNode()
    
    # Mentors
    m._mentors_manager.all.return_value = [MagicMock(), MagicMock()]
    
    # Mentees
    mentee_chain = m.menteemodule_set.select_related.return_value.filter.return_value
    mentee_chain.values_list.return_value = ["github_user_id_1", "github_user_id_2"]
    
    # Issues queryset for issue_mentees and interested_users
    m._issues_qs.filter.return_value.values_list.return_value = ["issue_id_1"]
    
    # Issues with select_related and prefetch_related for issues() method
    issues_query_chain = m._issues_qs.select_related.return_value.prefetch_related.return_value
    issues_query_chain.filter.return_value.order_by.return_value = [MagicMock()]
    
    # Issues count
    m._issues_qs.count.return_value = 5
    
    # Issue by number
    issue_by_num_chain = m._issues_qs.select_related.return_value.prefetch_related.return_value
    issue_by_num_chain.filter.return_value.first.return_value = MagicMock()
    
    return m

11-141: Consider maintenance implications of duplicating ModuleNode logic.

The FakeModuleNode class duplicates the business logic from the real ModuleNode implementation. While this approach successfully tests the resolver logic without database access (consistent with the preference for pure mock tests), it creates a maintenance burden:

  • Changes to the real ModuleNode require manual updates to FakeModuleNode
  • No automated verification that FakeModuleNode matches the real implementation
  • Risk of test/implementation drift over time

Alternative approaches to consider:

  1. Test the real ModuleNode with patched ORM calls - Import and test the actual ModuleNode class while patching database access, eliminating duplication.

  2. Extract business logic into testable functions - Move complex logic (like mentees(), issue_mentees()) into separate utility functions that can be tested independently, leaving ModuleNode as a thin GraphQL resolver layer.

  3. Document the maintenance contract - If keeping FakeModuleNode, add comments linking it to the real implementation and noting that both must be kept in sync.

This is not a defect, but something to consider for long-term maintainability as the mentorship module evolves.

Based on learnings: In this test suite, pure mock tests are preferred over Django TestCase with database interactions for model tests.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52d4306 and 5e1a7c9.

📒 Files selected for processing (2)
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • backend/tests/apps/mentorship/model/program_test.py
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:08.745Z
Learning: In the mentorship test suite (backend/tests/apps/mentorship/), the maintainer kart-u prefers pure mock tests over Django TestCase with database interactions for model tests.
📚 Learning: 2026-01-06T21:11:03.537Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:03.537Z
Learning: In backend/tests/apps/mentorship/, prefer pure mock tests (no real database interactions) for model tests. Avoid using Django TestCase with database access; implement tests with mocks/stubs to exercise logic and, when needed, lightweight fixtures without DB. This guideline targets all Python test files under the mentorship module.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • backend/tests/apps/mentorship/model/program_test.py
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 2000
File: backend/apps/mentorship/management/commands/sync_module_issues.py:109-145
Timestamp: 2025-08-17T11:55:55.990Z
Learning: In the OWASP/Nest mentorship system, tasks are designed to be assigned to only one assignee per issue, even if GitHub issues can have multiple assignees. The sync_module_issues command correctly uses issue.assignees.first() to create one task per issue for the first assignee only.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • backend/tests/apps/mentorship/model/program_test.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • backend/tests/apps/mentorship/model/program_test.py
🧬 Code graph analysis (2)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (3)
backend/apps/mentorship/api/internal/nodes/enum.py (1)
  • ExperienceLevelEnum (12-18)
backend/apps/mentorship/models/task.py (1)
  • Task (10-75)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-36)
backend/tests/apps/mentorship/model/program_test.py (2)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/tests/apps/mentorship/model/module_test.py (1)
  • test_str_returns_name (25-30)
🔇 Additional comments (1)
backend/tests/apps/mentorship/model/program_test.py (1)

1-15: LGTM! Tests are well-structured and follow project conventions.

Both test methods are correctly implemented:

  • test_program_status_choices verifies the ProgramStatus enum mappings match expected string values.
  • test_str_returns_name uses pure mocks to test the __str__ method, aligning with the preferred testing approach for this module.

The mock-based approach avoids database interactions while still testing the real implementation, which is exactly what's preferred in this test suite.

Based on learnings, pure mock tests are preferred for model tests in the mentorship module.

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 (2)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (2)

154-158: Redundant variable assignment.

mocked_query_prefetch is assigned twice on lines 154 and 157 with the same value. This appears to be copy-paste duplication.

♻️ Suggested cleanup
     mocked_query_prefetch = m._issues_qs.select_related.return_value.prefetch_related.return_value
     mocked_query_prefetch.filter.return_value.order_by.return_value = [MagicMock()]
     m._issues_qs.count.return_value = 5
-    mocked_query_prefetch = m._issues_qs.select_related.return_value.prefetch_related.return_value
     mocked_query_prefetch.filter.return_value.first.return_value = MagicMock()

373-379: Missing docstring for consistency.

This test function lacks a docstring, unlike all other test functions in this file.

♻️ Suggested fix
 def test_module_node_task_assigned_at_none(mock_module_node):
+    """Test task_assigned_at when no assignment is found."""
     with patch("apps.mentorship.models.task.Task.objects") as mock_task_objects:
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e1a7c9 and 650b9a2.

📒 Files selected for processing (1)
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:08.745Z
Learning: In the mentorship test suite (backend/tests/apps/mentorship/), the maintainer kart-u prefers pure mock tests over Django TestCase with database interactions for model tests.
📚 Learning: 2026-01-06T21:11:03.537Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:03.537Z
Learning: In backend/tests/apps/mentorship/, prefer pure mock tests (no real database interactions) for model tests. Avoid using Django TestCase with database access; implement tests with mocks/stubs to exercise logic and, when needed, lightweight fixtures without DB. This guideline targets all Python test files under the mentorship module.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 2000
File: backend/apps/mentorship/management/commands/sync_module_issues.py:109-145
Timestamp: 2025-08-17T11:55:55.990Z
Learning: In the OWASP/Nest mentorship system, tasks are designed to be assigned to only one assignee per issue, even if GitHub issues can have multiple assignees. The sync_module_issues command correctly uses issue.assignees.first() to create one task per issue for the first assignee only.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
🧬 Code graph analysis (1)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (3)
backend/apps/mentorship/api/internal/nodes/enum.py (1)
  • ExperienceLevelEnum (12-18)
backend/apps/mentorship/models/task.py (1)
  • Task (10-75)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-36)
🔇 Additional comments (1)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (1)

382-410: Good test coverage for input class defaults.

This test correctly validates the actual CreateModuleInput and UpdateModuleInput classes from production code, ensuring the domains, labels, and tags fields properly default to empty lists.

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

🤖 Fix all issues with AI agents
In
@backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py:
- Line 8: The import line importing CreateModuleInput,
UpdateModuleInput,ModuleNode has a missing space after the comma; update the
statement that references CreateModuleInput, UpdateModuleInput, and ModuleNode
so there is a space after the comma (i.e., put a space between
UpdateModuleInput, and ModuleNode) to conform to style and avoid linter errors.
🧹 Nitpick comments (1)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (1)

37-70: Add missing spaces after commas for PEP 8 compliance.

Several method calls are missing spaces after commas (lines 44, 50, 53, 59, 62, 66, 69). This affects readability and violates PEP 8 style guidelines.

✨ Proposed fixes
     def mock_issue_mentees(self, issue_number: int):
-        return ModuleNode.issue_mentees(self,issue_number)
+        return ModuleNode.issue_mentees(self, issue_number)

     def mock_issues(self, limit: int = 20, offset: int = 0, label: str | None = None):
-        return ModuleNode.issues(self,limit=limit,offset=offset,label=label)
+        return ModuleNode.issues(self, limit=limit, offset=offset, label=label)

     def mock_issues_count(self, label: str | None = None):
-        return ModuleNode.issues_count(self,label=label)
+        return ModuleNode.issues_count(self, label=label)

     def mock_issue_by_number(self, number: int):
-        return ModuleNode.issue_by_number(self,number=number)
+        return ModuleNode.issue_by_number(self, number=number)

     def mock_interested_users(self, issue_number: int):
-        return ModuleNode.interested_users(self,issue_number=issue_number)
+        return ModuleNode.interested_users(self, issue_number=issue_number)

     def mock_task_deadline(self, issue_number: int):
-        return ModuleNode.task_deadline(self,issue_number=issue_number)
+        return ModuleNode.task_deadline(self, issue_number=issue_number)

     def mock_task_assigned_at(self, issue_number: int):
-        return ModuleNode.task_assigned_at(self,issue_number=issue_number)
+        return ModuleNode.task_assigned_at(self, issue_number=issue_number)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 650b9a2 and 178e90b.

📒 Files selected for processing (1)
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:08.745Z
Learning: In the mentorship test suite (backend/tests/apps/mentorship/), the maintainer kart-u prefers pure mock tests over Django TestCase with database interactions for model tests.
📚 Learning: 2026-01-06T21:11:03.537Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:03.537Z
Learning: In backend/tests/apps/mentorship/, prefer pure mock tests (no real database interactions) for model tests. Avoid using Django TestCase with database access; implement tests with mocks/stubs to exercise logic and, when needed, lightweight fixtures without DB. This guideline targets all Python test files under the mentorship module.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 2000
File: backend/apps/mentorship/management/commands/sync_module_issues.py:109-145
Timestamp: 2025-08-17T11:55:55.990Z
Learning: In the OWASP/Nest mentorship system, tasks are designed to be assigned to only one assignee per issue, even if GitHub issues can have multiple assignees. The sync_module_issues command correctly uses issue.assignees.first() to create one task per issue for the first assignee only.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
🧬 Code graph analysis (1)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (4)
backend/apps/mentorship/api/internal/nodes/enum.py (1)
  • ExperienceLevelEnum (12-18)
backend/apps/mentorship/api/internal/nodes/mentor.py (2)
  • name (18-20)
  • login (23-25)
backend/apps/github/api/internal/nodes/issue.py (1)
  • labels (50-52)
backend/apps/mentorship/api/internal/nodes/module.py (7)
  • mentors (38-40)
  • mentees (43-51)
  • issue_mentees (54-68)
  • available_labels (100-108)
  • issue_by_number (111-118)
  • task_deadline (134-145)
  • task_assigned_at (148-159)
🔇 Additional comments (2)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (2)

92-310: Excellent test coverage with appropriate mocking strategy.

The tests comprehensively cover ModuleNode behavior including edge cases (no issues, no project, no deadline, etc.) while using pure mocks to avoid database interactions. This approach aligns well with the project's testing preferences.

Based on learnings, the maintainer prefers pure mock tests over Django TestCase with database interactions for the mentorship module tests.


312-340: Good coverage of default values for input types.

The test verifies that CreateModuleInput and UpdateModuleInput properly default domains, labels, and tags to empty lists, preventing potential None-related errors in downstream code.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 8, 2026

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

🧹 Nitpick comments (1)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (1)

264-287: Consider consistent mock variable naming.

Tests correctly validate deadline retrieval logic. Minor suggestion: the variable naming for mock_task_order_by differs between the two tests:

  • Line 267: assigns the queryset result (.order_by.return_value)
  • Line 282: assigns the order_by method (.order_by)

While both approaches work, using consistent naming would improve readability.

♻️ Proposed consistency improvement
 def test_module_node_task_deadline_none(mock_module_node):
     """Test task_deadline when no deadline is found."""
     with patch("apps.mentorship.models.task.Task.objects") as mock_task_objects:
-        mock_task_order_by = mock_task_objects.filter.return_value.order_by
-        mock_task_order_by.return_value.values_list.return_value.first.return_value = None
+        mock_task_order_by = mock_task_objects.filter.return_value.order_by.return_value
+        mock_task_order_by.values_list.return_value.first.return_value = None
 
         deadline = mock_module_node.mock_task_deadline(issue_number=101)
         assert deadline is None
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 178e90b and ed77a1f.

📒 Files selected for processing (2)
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py
✅ Files skipped from review due to trivial changes (1)
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_program_test.py
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:08.745Z
Learning: In the mentorship test suite (backend/tests/apps/mentorship/), the maintainer kart-u prefers pure mock tests over Django TestCase with database interactions for model tests.
📚 Learning: 2026-01-06T21:11:03.537Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3179
File: backend/tests/apps/mentorship/model/test_program.py:13-22
Timestamp: 2026-01-06T21:11:03.537Z
Learning: In backend/tests/apps/mentorship/, prefer pure mock tests (no real database interactions) for model tests. Avoid using Django TestCase with database access; implement tests with mocks/stubs to exercise logic and, when needed, lightweight fixtures without DB. This guideline targets all Python test files under the mentorship module.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 2000
File: backend/apps/mentorship/management/commands/sync_module_issues.py:109-145
Timestamp: 2025-08-17T11:55:55.990Z
Learning: In the OWASP/Nest mentorship system, tasks are designed to be assigned to only one assignee per issue, even if GitHub issues can have multiple assignees. The sync_module_issues command correctly uses issue.assignees.first() to create one task per issue for the first assignee only.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2025-07-11T15:57:56.648Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/queries/module.py:39-50
Timestamp: 2025-07-11T15:57:56.648Z
Learning: In the OWASP Nest mentorship GraphQL queries, Strawberry GraphQL automatically converts between Django Module instances and ModuleNode types, so methods can return Module instances directly without manual conversion even when typed as ModuleNode.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2025-07-13T05:55:46.436Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/program.py:166-166
Timestamp: 2025-07-13T05:55:46.436Z
Learning: In the OWASP Nest mentorship GraphQL mutations, Strawberry GraphQL automatically converts between Django Program instances and ProgramNode types, so mutations can return Program instances directly without manual conversion even when typed as ProgramNode, similar to the Module/ModuleNode pattern.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
🧬 Code graph analysis (1)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (4)
backend/apps/mentorship/api/internal/nodes/enum.py (1)
  • ExperienceLevelEnum (12-18)
backend/apps/mentorship/api/internal/nodes/mentor.py (2)
  • name (18-20)
  • login (23-25)
backend/apps/github/api/internal/nodes/issue.py (1)
  • labels (50-52)
backend/apps/mentorship/api/internal/nodes/module.py (7)
  • mentors (38-40)
  • mentees (43-51)
  • issue_mentees (54-68)
  • available_labels (100-108)
  • issue_by_number (111-118)
  • task_deadline (134-145)
  • task_assigned_at (148-159)
🔇 Additional comments (13)
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py (13)

1-12: LGTM!

Imports are well-organized and appropriate for the test suite.


15-72: Excellent alignment with testing preferences!

The FakeModuleNode test double effectively enables testing actual ModuleNode resolver logic without database interactions. This approach correctly implements the pure-mock testing strategy preferred for the mentorship module.

Based on learnings, the maintainer prefers pure mock tests over Django TestCase with database interactions for model tests.


74-91: LGTM!

The fixture properly configures mock querysets with appropriate return values, supporting the comprehensive test scenarios that follow.


94-109: LGTM!

Comprehensive validation of field assignments.


111-132: LGTM!

Both tests correctly validate resolver behavior and queryset operations without database access.


134-161: LGTM!

Thorough coverage of issue_mentees behavior including the edge case where no issue is found.


163-173: LGTM!

Proper handling of both present and absent project relationships.


175-198: LGTM!

Comprehensive coverage of issues querying with and without label filters.


200-213: LGTM!

Correct validation of label filtering and retrieval logic.


215-229: LGTM!

Thorough validation of queryset optimization with select_related and prefetch_related.


231-262: LGTM!

Proper coverage of interested users resolution including edge case handling.


289-312: LGTM!

Proper coverage of assignment timestamp retrieval. The same optional naming consistency suggestion from the task_deadline tests applies here (lines 292 vs 307).


314-342: Default list fields correctly initialized to empty lists.

The test correctly validates that domains, labels, and tags in both CreateModuleInput and UpdateModuleInput default to empty lists via strawberry.field(default_factory=list), preventing common bugs with None values.

@kart-u kart-u marked this pull request as draft January 8, 2026 15:03
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/mentorship/* test coverage

1 participant