Skip to content

feat: improve backend test coverage to 100%#3991

Merged
arkid15r merged 9 commits intoOWASP:mainfrom
HarshitVerma109:feat/improve-test-coverage-100
Feb 18, 2026
Merged

feat: improve backend test coverage to 100%#3991
arkid15r merged 9 commits intoOWASP:mainfrom
HarshitVerma109:feat/improve-test-coverage-100

Conversation

@HarshitVerma109
Copy link
Contributor

Proposed change

Resolves #3978
Improved backend test coverage to 100%

Checklist

  • Required: I followed the contributing workflow
  • Required: I verified that my code works as intended and resolves the issue as described
  • Required: I ran make check-test locally: all warnings addressed, tests passed
  • I used AI for code, documentation, tests, or communication related to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds many new unit tests across OWASP, GitHub, Slack, and API modules; initializes a YAML metadata dict before parsing in owasp_sync_posts; and simplifies leader-email extraction logic in get_leaders_emails.

Changes

Cohort / File(s) Summary
OWASP production changes
backend/apps/owasp/management/commands/owasp_sync_posts.py, backend/apps/owasp/models/common.py
Initialize metadata: dict[str, Any] = {} before YAML parsing in sync posts; simplify get_leaders_emails loop to always derive name/value and assign None for non-email values.
OWASP management command tests
backend/tests/apps/owasp/management/commands/*_test.py
Add/expand many CLI argument-parsing tests and edge-case/error-path tests for aggregate, snapshot, enrich, scrape, sync posts, update leaders, metadata generation; standardize assertions to assert_called variants.
OWASP index & search tests
backend/tests/apps/owasp/index/.../*.py, backend/tests/apps/owasp/index/search/*_test.py
Add tests for registry index get_entities and ensure search param propagation (restrictSearchableAttributes, distinct) to raw_search; convert many tests to class-based structure.
OWASP model & utility tests
backend/tests/apps/owasp/models/*_test.py, backend/tests/apps/owasp/models/common_test.py
Add/extend tests for Event date parsing, Sponsor ordering, ProgramAdmin.str, URL parsing, and leader-email edge cases.
Slack app, handlers & command tests
backend/tests/apps/slack/apps_test.py, backend/tests/apps/slack/commands/*_test.py, backend/tests/apps/slack/common/handlers/*_test.py
Add tests for Bolt app registration, error/log handlers, command registration, and ensure pagination controls omitted when single-page results for chapters/committees/projects/users.
Slack messages & utils tests
backend/tests/apps/slack/management/commands/slack_sync_messages_test.py, backend/tests/apps/slack/utils_test.py, backend/tests/apps/slack/events/event_test.py
Massive coverage additions for _sync_user_messages (retries, rate-limits, errors, pagination, bot/user handling) and blocks→text/SECTION_BREAK behavior; refactor tests into class-based layouts.
GitHub command & model tests
backend/tests/apps/github/management/commands/*_test.py, backend/tests/apps/github/models/*_test.py
Refactor many tests to class-based structure and add coverage for repo sync/enrich flows, argument parsing, bulk save, and PullRequest create/update paths.
API & misc tests
backend/tests/apps/api/..., backend/tests/apps/common/management/commands/add_project_custom_tags_test.py, backend/tests/apps/mentorship/model/program_admin_test.py
Convert many module-level tests into class-based tests; add small CLI arg parsing test and ProgramAdmin.str test.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • kasya
  • arkid15r
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: improve backend test coverage to 100%' accurately summarizes the main objective of the pull request.
Description check ✅ Passed The description clearly relates to the changeset, referencing issue #3978 and confirming that backend test coverage has been improved to 100%.
Linked Issues check ✅ Passed The pull request meets the requirements of issue #3978 by implementing comprehensive test coverage for all specified modules [#3978].
Out of Scope Changes check ✅ Passed All changes are within scope of issue #3978 objectives—adding test coverage to specified files with no unrelated modifications detected.

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

✨ Finishing Touches
🧪 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.

cubic-dev-ai[bot]
cubic-dev-ai bot previously approved these changes Feb 18, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 33 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

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

🧹 Nitpick comments (14)
backend/tests/apps/owasp/index/search/chapter_test.py (1)

41-42: Optional: prefer call_args.args over call_args[0] for positional-arg inspection.

call_args[0] (the legacy subscript) and call_args.args (Python 3.8+ named attribute) are functionally identical here, but call_args.args is more self-documenting and avoids the "magic index" feel. The same pattern applies to the identical blocks in issue_test.py and project_test.py.

♻️ Proposed refactor
-        call_args = mock_raw_search.call_args[0]
-        params = call_args[2]
+        params = mock_raw_search.call_args.args[2]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/owasp/index/search/chapter_test.py` around lines 41 - 42,
Replace the legacy positional-access pattern using mock_raw_search.call_args[0]
with the more explicit mock_raw_search.call_args and its .args attribute: assign
call_args = mock_raw_search.call_args and then use params = call_args.args[2]
(instead of call_args[2]); apply the same change in the analogous blocks in
issue_test.py and project_test.py so positional-arg inspection uses
call_args.args throughout.
backend/tests/apps/slack/utils_test.py (3)

223-223: Trailing underscore in test function name test_get_gsoc_projects_.

The extra _ suffix is inconsistent with all other test function names in this file.

♻️ Proposed fix
-def test_get_gsoc_projects_(monkeypatch):
+def test_get_gsoc_projects(monkeypatch):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/slack/utils_test.py` at line 223, The test function is
misnamed with a trailing underscore: rename test_get_gsoc_projects_ to
test_get_gsoc_projects to match the naming convention; update any references or
test discovery expectations that call test_get_gsoc_projects_ (e.g., in the file
backend/tests/apps/slack/utils_test.py) so they point to the new name and run
the tests to ensure no references remain.

334-345: assert result is not None is a trivially weak assertion.

Both test_get_sponsors_data and test_get_posts_data set up __getitem__ on a mock queryset but never check what result actually contains — the assertion passes even if the function returns any truthy object. Consider asserting on the returned slice or at minimum verifying __getitem__ was invoked with the expected argument.

♻️ Proposed fix
 # test_get_sponsors_data
     result = get_sponsors_data(limit=5)
     mock_objects.all.assert_called_once()
-    assert result is not None
+    mock_queryset.__getitem__.assert_called_once_with(slice(None, 5))

 # test_get_posts_data
     result = get_posts_data(limit=3)
     mock_recent.assert_called_once()
-    assert result is not None
+    mock_queryset.__getitem__.assert_called_once_with(slice(None, 3))

Also applies to: 357-369

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/slack/utils_test.py` around lines 334 - 345, The test uses
a mock queryset with __getitem__ but only asserts result is not None; update
test_get_sponsors_data to assert that mock_queryset.__getitem__ was called with
the expected slice (e.g. slice(0, 5) or equivalent) and that the returned value
equals the mocked slice value (mock_sponsor list) from get_sponsors_data; locate
the test and its mock setup (mock_queryset and the patch of
apps.owasp.models.sponsor.Sponsor.objects) and add assertions for
mock_queryset.__getitem__.assert_called_once_with(slice(0, 5)) and assert result
== [mock_sponsor] (or the exact mocked return) to validate slicing behavior of
get_sponsors_data.

252-276: length variable is reused for unrelated semantic purposes, masking potential assertion bugs.

length is first set to 3 (expected news-item count), reassigned to 2 for the limit argument, and then the same length variable is used as the expected mock_get.call_count. The assertion passes by coincidence — if the call count or the limit ever change independently, the assertion will silently test the wrong thing.

♻️ Proposed fix
     result = get_news_data()
-    length = 3
-    assert len(result) == length
+    assert len(result) == 3
     assert result[0]["title"] == "News Title 1"
     assert result[0]["author"] == "Author 1"
     assert result[0]["url"] == urljoin(OWASP_NEWS_URL, "/news1")

-    length = 2
-    result_limited = get_news_data(limit=2)
-    assert len(result_limited) == length
+    result_limited = get_news_data(limit=2)
+    assert len(result_limited) == 2

     mock_get.assert_called_with(OWASP_NEWS_URL, timeout=30)
     result2 = get_news_data()
-    assert mock_get.call_count == length
+    assert mock_get.call_count == 2  # third call uses cache
     assert result == result2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/slack/utils_test.py` around lines 252 - 276, The test
reuses the variable name length for three different meanings which masks bugs;
update test_get_news_data to use distinct, explicit variables (e.g.,
expected_total_items = 3, expected_limited_items = 2, expected_get_calls = 2)
and change assertions to use those names: assert len(result) ==
expected_total_items, assert len(result_limited) == expected_limited_items, and
assert mock_get.call_count == expected_get_calls; keep references to mock_get,
get_news_data, and OWASP_NEWS_URL unchanged.
backend/tests/apps/owasp/api/internal/queries/sponsor_test.py (2)

21-21: Redundant list() call — sponsors() already returns a list.

sorted() returns a list, so list(query.sponsors()) just copies it unnecessarily.

♻️ Proposed cleanup
-            result = list(query.sponsors())
+            result = query.sponsors()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/owasp/api/internal/queries/sponsor_test.py` at line 21,
The code creates an unnecessary copy by calling list() on the result of
query.sponsors(); remove the redundant list() wrapper and assign result =
query.sponsors() (or use result = sorted(query.sponsors()) only where sorting is
needed) so the test uses the original list returned by the sponsors() method
without an extra allocation; locate the usage in the test where result is
assigned from query.sponsors() and update that assignment accordingly.

8-25: Add test cases for the remaining sponsor types (GOLD, SUPPORTER, NOT_SPONSOR).

The test only exercises 3 of the 6 SponsorType values (DIAMOND=1, PLATINUM=2, SILVER=4). The priority mappings for GOLD (3), SUPPORTER (5), and NOT_SPONSOR (6) are never validated. If those dictionary entries in sponsors() were wrong or accidentally swapped, this test would not catch it.

♻️ Suggested extension
 class TestSponsorQuery:
     def test_sponsors_returns_sorted_by_type(self):
         """Test sponsors resolver sorts by sponsor type priority."""
         diamond = MagicMock()
         diamond.sponsor_type = Sponsor.SponsorType.DIAMOND

         silver = MagicMock()
         silver.sponsor_type = Sponsor.SponsorType.SILVER

         platinum = MagicMock()
         platinum.sponsor_type = Sponsor.SponsorType.PLATINUM

+        gold = MagicMock()
+        gold.sponsor_type = Sponsor.SponsorType.GOLD
+
+        supporter = MagicMock()
+        supporter.sponsor_type = Sponsor.SponsorType.SUPPORTER
+
+        not_sponsor = MagicMock()
+        not_sponsor.sponsor_type = Sponsor.SponsorType.NOT_SPONSOR
+
-        with patch.object(Sponsor.objects, "all", return_value=[silver, diamond, platinum]):
+        with patch.object(
+            Sponsor.objects,
+            "all",
+            return_value=[silver, not_sponsor, gold, diamond, supporter, platinum],
+        ):
             query = SponsorQuery()
             result = list(query.sponsors())

         assert result[0] == diamond
         assert result[1] == platinum
-        assert result[2] == silver
+        assert result[2] == gold
+        assert result[3] == silver
+        assert result[4] == supporter
+        assert result[5] == not_sponsor
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/owasp/api/internal/queries/sponsor_test.py` around lines 8
- 25, Extend test_sponsors_returns_sorted_by_type to include MagicMock instances
for Sponsor.SponsorType.GOLD, Sponsor.SponsorType.SUPPORTER, and
Sponsor.SponsorType.NOT_SPONSOR, set their sponsor_type attributes accordingly,
include them in the patched Sponsor.objects.all return_value alongside the
existing diamond/platinum/silver in a mixed order, then call
SponsorQuery().sponsors() and assert the full result list order matches the
expected priority (DIAMOND, PLATINUM, GOLD, SILVER, SUPPORTER, NOT_SPONSOR) to
validate the complete priority mapping in SponsorQuery.sponsors().
backend/tests/apps/owasp/management/commands/common/entity_metadata_test.py (1)

100-103: Prefer a real instance over None as the self argument.

Passing None as self works only because the current body never dereferences self. If the base implementation is later extended to call any method on self, this test will raise an AttributeError at runtime without any obvious connection to the change that broke it. Use an instantiable concrete subclass already present in the file:

♻️ Suggested fix
-        result = EntityMetadataBase.get_metadata(None, entity=MagicMock())
+        result = EntityMetadataBase.get_metadata(ConcreteTestCommand(), entity=MagicMock())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/owasp/management/commands/common/entity_metadata_test.py`
around lines 100 - 103, The test test_get_metadata_abstract_returns_empty_dict
should not pass None as the self arg to EntityMetadataBase.get_metadata; instead
instantiate and use an existing concrete, instantiable subclass defined in the
test file (replace the None in the call EntityMetadataBase.get_metadata(None,
entity=MagicMock()) with an instance of that concrete subclass), so the test
uses a real object and will not break if the base implementation later calls
methods on self.
backend/tests/apps/common/management/commands/add_project_custom_tags_test.py (1)

13-19: Consider adding an inline comment explaining the getattr usage.

getattr(args, "file-name") is correct — argparse stores positional argument values under their literal dest string, and since args.file-name is a syntax error (parsed as subtraction), getattr is required. However, this is non-obvious to a reader; a short inline comment would prevent future confusion.

✨ Optional clarification comment
-        assert getattr(args, "file-name") == "test-file.json"
+        # `args.file-name` is a syntax error; getattr is required for hyphenated dest names.
+        assert getattr(args, "file-name") == "test-file.json"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/tests/apps/common/management/commands/add_project_custom_tags_test.py`
around lines 13 - 19, The test test_add_arguments in the Command class uses
getattr(args, "file-name") which looks odd because args.file-name is a syntax
error; add a brief inline comment next to that assertion explaining that
argparse stores positional arguments under their literal dest (including
hyphens) and therefore getattr is required to access the "file-name"
dest—reference the add_arguments method/Command class and the args variable in
the comment for clarity.
backend/tests/apps/owasp/management/commands/owasp_create_member_snapshot_test.py (2)

514-515: Misleading test name — rename to match the actual scenario.

The method is named test_generate_heatmap_data_empty_issues but it passes a non-empty issue list with an out-of-range date; the [] in the call is the PR list. The docstring correctly says "issue outside date range". The name should reflect that.

✏️ Suggested rename
-    def test_generate_heatmap_data_empty_issues(self, command):
-        """Test generate_heatmap_data with issue outside date range."""
+    def test_generate_heatmap_data_issue_out_of_range(self, command):
+        """Test generate_heatmap_data filters out issue outside date range."""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/tests/apps/owasp/management/commands/owasp_create_member_snapshot_test.py`
around lines 514 - 515, Rename the test method
test_generate_heatmap_data_empty_issues to clearly reflect that it tests an
issue outside the date range (e.g.,
test_generate_heatmap_data_issue_outside_date_range); update the method name and
any references to it so the test name matches the docstring "issue outside date
range" and adjust any test discovery references if present in tests or CI.

962-975: Missing generate_channel_communications patch — inconsistent with parallel test.

test_handle_with_slack_messages (lines 843–845) explicitly mocks generate_channel_communications, but test_handle_with_slack_zero_messages does not. If the production code invokes generate_channel_communications regardless of message count, the real implementation runs here on a MagicMock queryset (which iterates to empty by default). While functionally harmless today, it silently exercises real code paths in a unit test and will break if the production implementation changes. The missing explicit __iter__ setup (present in test_handle_with_slack_messages at line 833) adds to the inconsistency.

✏️ Suggested addition for consistency and isolation
     mock_msg_qs = mock.MagicMock()
     mock_msg_qs.select_related.return_value = mock_msg_qs
     mock_msg_qs.count.return_value = 0
+    mock_msg_qs.__iter__ = lambda _: iter([])
     mock_message.objects.filter.return_value = mock_msg_qs
     mock_message.objects.none.return_value = mock_msg_qs

     mocker.patch.object(command, "generate_heatmap_data", return_value={})
     mocker.patch.object(command, "generate_entity_contributions", return_value={})
     mocker.patch.object(command, "generate_repository_contributions", return_value={})
     mocker.patch.object(command, "generate_communication_heatmap_data", return_value={})
+    mocker.patch.object(command, "generate_channel_communications", return_value={})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/tests/apps/owasp/management/commands/owasp_create_member_snapshot_test.py`
around lines 962 - 975, In test_handle_with_slack_zero_messages, mock out
generate_channel_communications and ensure the mocked queryset iteration is
explicit: patch command.generate_channel_communications to return {} (like the
other generators), and set mock_msg_qs.__iter__.return_value = iter([]) on the
MagicMock (the same pattern used in test_handle_with_slack_messages) so the real
implementation isn't exercised when calling command.handle(username="testuser",
...); keep references to mock_message, mock_msg_qs,
command.generate_channel_communications, command.handle, and
mock_snapshot.messages.add to locate where to apply the changes.
backend/tests/apps/slack/management/commands/slack_sync_messages_test.py (1)

951-962: Repeated rate-limit error construction — consider a shared fixture

The SlackApiError setup block (constructing a MagicMock response with __getitem__, .get, and .headers) is duplicated identically in both TestFetchRepliesRateLimit methods and in several earlier test classes. A class- or module-level pytest fixture would keep these DRY.

♻️ Example fixture
`@pytest.fixture`
def rate_limit_error():
    error = SlackApiError(
        response={"ok": False, "error": "ratelimited"},
        message="Rate limited",
    )
    error.response = MagicMock()
    error.response.__getitem__ = lambda _self, key: (
        "ratelimited" if key == "error" else None
    )
    error.response.get = lambda key, default=None: (
        "ratelimited" if key == "error" else default
    )
    error.response.headers = {"Retry-After": "1"}
    return error

Also applies to: 996-1007

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/slack/management/commands/slack_sync_messages_test.py`
around lines 951 - 962, Extract the duplicated SlackApiError construction (the
block creating SlackApiError, assigning error.response = MagicMock, setting
response.__getitem__, response.get, and response.headers) into a reusable pytest
fixture (e.g., rate_limit_error) at module scope (or in conftest.py) and update
the TestFetchRepliesRateLimit test methods (and other tests around the
duplicated blocks) to accept that fixture instead of rebuilding the mock; locate
occurrences by the SlackApiError instantiation and the MagicMock response setup
and replace them with the fixture injection.
backend/tests/apps/owasp/management/commands/owasp_update_leaders_test.py (1)

248-249: User patch is unreachable in this code path.

handle() returns early at the model_name not in model_map guard before it ever reaches User.objects.values(...), so the with patch(...) context manager here is dead code and may mislead future readers.

🧹 Suggested cleanup
-    with patch(f"{COMMAND_PATH}.User"):
-        cmd.handle(model_name="invalid", threshold=75)
+    cmd.handle(model_name="invalid", threshold=75)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/owasp/management/commands/owasp_update_leaders_test.py`
around lines 248 - 249, The test currently patches User inside a call to
cmd.handle(model_name="invalid", threshold=75) but cmd.handle returns early due
to the model_name not in model_map guard, making the with
patch(f"{COMMAND_PATH}.User") unreachable; remove the dead with patch(...) block
(or move/replace it into a separate test that calls cmd.handle with a valid
model_name so User.objects.values(...) is exercised) and keep the assertion(s)
that verify the early-return behavior of cmd.handle(model_name="invalid")
instead.
backend/tests/apps/owasp/management/commands/owasp_sync_posts_test.py (1)

218-239: Core assertions are correct; consider adding a stderr assertion for completeness.

The test correctly exercises the no-YAML-match path and verifies update_data is never called and bulk_save receives an empty list. The parallel test test_handle_missing_required_fields (line 216) additionally asserts command.stderr.write.assert_called() since both paths go through the same warning branch. Adding the same assertion here would align the two tests and explicitly confirm the warning is emitted.

♻️ Suggested addition
         assert mock_get_content.call_count == 2
         mock_update_data.assert_not_called()
         mock_bulk_save.assert_called_once_with([])
+        command.stderr.write.assert_called()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/owasp/management/commands/owasp_sync_posts_test.py` around
lines 218 - 239, Add an assertion that the command wrote a warning to stderr in
the no-YAML-match test: after calling command.handle() in
test_handle_no_yaml_match, assert that command.stderr.write.assert_called() to
mirror test_handle_missing_required_fields and confirm the warning branch was
exercised; keep existing assertions for mock_get_content.call_count,
mock_update_data.assert_not_called(), and
mock_bulk_save.assert_called_once_with([]).
backend/tests/apps/owasp/management/commands/owasp_aggregate_entity_contributions_test.py (1)

460-462: assert_called() is weaker than the parallel test uses — consider assert_called_once().

test_handle_with_offset (line 427) uses mock_aggregate.assert_called_once() for the same conceptual check. assert_called() on line 462 would pass even if aggregate_contributions were called multiple times, reducing diagnostic value.

♻️ Proposed fix
-        mock_aggregate.assert_called()
+        mock_aggregate.assert_called_once()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/tests/apps/owasp/management/commands/owasp_aggregate_entity_contributions_test.py`
around lines 460 - 462, Replace the weak mock assertion with a stronger one:
change the call to mock_aggregate.assert_called() in the test (the assertion
verifying aggregate_contributions was invoked) to
mock_aggregate.assert_called_once() so the test ensures aggregate_contributions
was invoked exactly once (consistent with the other test test_handle_with_offset
that uses assert_called_once()).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/apps/owasp/models/common.py`:
- Around line 240-242: get_leaders_emails is returning arbitrary markdown link
URLs (e.g. https://...) as member_email which later causes Django EmailField
validation errors in sync_leaders/EntityMember.update_data; change the
extraction to only capture valid email addresses or mailto: links and normalize
them to plain emails (e.g. extract after "mailto:") and return an empty string
for non-email links so sync_leaders doesn't set invalid emails, or alternatively
decide to store arbitrary contact URLs by switching member_email to a
CharField—update get_leaders_emails (and the regex it uses) to enforce the
chosen behavior.

In `@backend/tests/apps/owasp/index/registry/chapter_test.py`:
- Line 9: The patch.object call uses a lambda with parameter name self which is
unused, causing Ruff ARG005; update the lambda to use a throwaway parameter name
(e.g., replace "lambda self: None" with "lambda _: None") in the patch for
ChapterIndex.__init__ so the signature still accepts the instance but the linter
is satisfied.

In `@backend/tests/apps/owasp/index/registry/committee_test.py`:
- Line 9: Replace the unused-argument lambda in the patch call so it doesn't
trigger Ruff ARG005: change the current patch.object(CommitteeIndex, "__init__",
lambda self: None) to use a no-op that accepts any args, e.g.
patch.object(CommitteeIndex, "__init__", lambda *args, **kwargs: None), keeping
the target class/method name CommitteeIndex.__init__ unchanged.

In `@backend/tests/apps/slack/apps_test.py`:
- Line 68: The test unpacking of _reload_with_mock_app() assigns an unused
variable named module; update both occurrences in
test_log_events_creates_event_and_calls_next and
test_log_events_handles_exception so the unpacking uses _module (or simply _ )
instead of module (e.g., change "handlers, module =
self._reload_with_mock_app()" to "handlers, _module =
self._reload_with_mock_app()" or "handlers, _ = ...") to satisfy Ruff RUF059 and
make the intent explicit.

In `@backend/tests/apps/slack/management/commands/slack_sync_messages_test.py`:
- Around line 757-789: The test currently only runs Command._sync_user_messages
but makes no assertions that messages missing a channel were skipped; update the
test_sync_message_no_channel_id to assert that
Conversation.objects.get_or_create (or Conversation.objects.get_or_create mocked
via Workspace/Conversation) was never called and that Command._create_message
(or whatever internal method creates messages) was not invoked for the match
with an empty channel, by patching/mocking Conversation.objects.get_or_create
and Command._create_message (or _create_message) and adding
assert_not_called/assert_called_with checks to confirm no conversation or
message creation occurred for the match with channel == {}.

---

Nitpick comments:
In
`@backend/tests/apps/common/management/commands/add_project_custom_tags_test.py`:
- Around line 13-19: The test test_add_arguments in the Command class uses
getattr(args, "file-name") which looks odd because args.file-name is a syntax
error; add a brief inline comment next to that assertion explaining that
argparse stores positional arguments under their literal dest (including
hyphens) and therefore getattr is required to access the "file-name"
dest—reference the add_arguments method/Command class and the args variable in
the comment for clarity.

In `@backend/tests/apps/owasp/api/internal/queries/sponsor_test.py`:
- Line 21: The code creates an unnecessary copy by calling list() on the result
of query.sponsors(); remove the redundant list() wrapper and assign result =
query.sponsors() (or use result = sorted(query.sponsors()) only where sorting is
needed) so the test uses the original list returned by the sponsors() method
without an extra allocation; locate the usage in the test where result is
assigned from query.sponsors() and update that assignment accordingly.
- Around line 8-25: Extend test_sponsors_returns_sorted_by_type to include
MagicMock instances for Sponsor.SponsorType.GOLD, Sponsor.SponsorType.SUPPORTER,
and Sponsor.SponsorType.NOT_SPONSOR, set their sponsor_type attributes
accordingly, include them in the patched Sponsor.objects.all return_value
alongside the existing diamond/platinum/silver in a mixed order, then call
SponsorQuery().sponsors() and assert the full result list order matches the
expected priority (DIAMOND, PLATINUM, GOLD, SILVER, SUPPORTER, NOT_SPONSOR) to
validate the complete priority mapping in SponsorQuery.sponsors().

In `@backend/tests/apps/owasp/index/search/chapter_test.py`:
- Around line 41-42: Replace the legacy positional-access pattern using
mock_raw_search.call_args[0] with the more explicit mock_raw_search.call_args
and its .args attribute: assign call_args = mock_raw_search.call_args and then
use params = call_args.args[2] (instead of call_args[2]); apply the same change
in the analogous blocks in issue_test.py and project_test.py so positional-arg
inspection uses call_args.args throughout.

In `@backend/tests/apps/owasp/management/commands/common/entity_metadata_test.py`:
- Around line 100-103: The test test_get_metadata_abstract_returns_empty_dict
should not pass None as the self arg to EntityMetadataBase.get_metadata; instead
instantiate and use an existing concrete, instantiable subclass defined in the
test file (replace the None in the call EntityMetadataBase.get_metadata(None,
entity=MagicMock()) with an instance of that concrete subclass), so the test
uses a real object and will not break if the base implementation later calls
methods on self.

In
`@backend/tests/apps/owasp/management/commands/owasp_aggregate_entity_contributions_test.py`:
- Around line 460-462: Replace the weak mock assertion with a stronger one:
change the call to mock_aggregate.assert_called() in the test (the assertion
verifying aggregate_contributions was invoked) to
mock_aggregate.assert_called_once() so the test ensures aggregate_contributions
was invoked exactly once (consistent with the other test test_handle_with_offset
that uses assert_called_once()).

In
`@backend/tests/apps/owasp/management/commands/owasp_create_member_snapshot_test.py`:
- Around line 514-515: Rename the test method
test_generate_heatmap_data_empty_issues to clearly reflect that it tests an
issue outside the date range (e.g.,
test_generate_heatmap_data_issue_outside_date_range); update the method name and
any references to it so the test name matches the docstring "issue outside date
range" and adjust any test discovery references if present in tests or CI.
- Around line 962-975: In test_handle_with_slack_zero_messages, mock out
generate_channel_communications and ensure the mocked queryset iteration is
explicit: patch command.generate_channel_communications to return {} (like the
other generators), and set mock_msg_qs.__iter__.return_value = iter([]) on the
MagicMock (the same pattern used in test_handle_with_slack_messages) so the real
implementation isn't exercised when calling command.handle(username="testuser",
...); keep references to mock_message, mock_msg_qs,
command.generate_channel_communications, command.handle, and
mock_snapshot.messages.add to locate where to apply the changes.

In `@backend/tests/apps/owasp/management/commands/owasp_sync_posts_test.py`:
- Around line 218-239: Add an assertion that the command wrote a warning to
stderr in the no-YAML-match test: after calling command.handle() in
test_handle_no_yaml_match, assert that command.stderr.write.assert_called() to
mirror test_handle_missing_required_fields and confirm the warning branch was
exercised; keep existing assertions for mock_get_content.call_count,
mock_update_data.assert_not_called(), and
mock_bulk_save.assert_called_once_with([]).

In `@backend/tests/apps/owasp/management/commands/owasp_update_leaders_test.py`:
- Around line 248-249: The test currently patches User inside a call to
cmd.handle(model_name="invalid", threshold=75) but cmd.handle returns early due
to the model_name not in model_map guard, making the with
patch(f"{COMMAND_PATH}.User") unreachable; remove the dead with patch(...) block
(or move/replace it into a separate test that calls cmd.handle with a valid
model_name so User.objects.values(...) is exercised) and keep the assertion(s)
that verify the early-return behavior of cmd.handle(model_name="invalid")
instead.

In `@backend/tests/apps/slack/management/commands/slack_sync_messages_test.py`:
- Around line 951-962: Extract the duplicated SlackApiError construction (the
block creating SlackApiError, assigning error.response = MagicMock, setting
response.__getitem__, response.get, and response.headers) into a reusable pytest
fixture (e.g., rate_limit_error) at module scope (or in conftest.py) and update
the TestFetchRepliesRateLimit test methods (and other tests around the
duplicated blocks) to accept that fixture instead of rebuilding the mock; locate
occurrences by the SlackApiError instantiation and the MagicMock response setup
and replace them with the fixture injection.

In `@backend/tests/apps/slack/utils_test.py`:
- Line 223: The test function is misnamed with a trailing underscore: rename
test_get_gsoc_projects_ to test_get_gsoc_projects to match the naming
convention; update any references or test discovery expectations that call
test_get_gsoc_projects_ (e.g., in the file
backend/tests/apps/slack/utils_test.py) so they point to the new name and run
the tests to ensure no references remain.
- Around line 334-345: The test uses a mock queryset with __getitem__ but only
asserts result is not None; update test_get_sponsors_data to assert that
mock_queryset.__getitem__ was called with the expected slice (e.g. slice(0, 5)
or equivalent) and that the returned value equals the mocked slice value
(mock_sponsor list) from get_sponsors_data; locate the test and its mock setup
(mock_queryset and the patch of apps.owasp.models.sponsor.Sponsor.objects) and
add assertions for mock_queryset.__getitem__.assert_called_once_with(slice(0,
5)) and assert result == [mock_sponsor] (or the exact mocked return) to validate
slicing behavior of get_sponsors_data.
- Around line 252-276: The test reuses the variable name length for three
different meanings which masks bugs; update test_get_news_data to use distinct,
explicit variables (e.g., expected_total_items = 3, expected_limited_items = 2,
expected_get_calls = 2) and change assertions to use those names: assert
len(result) == expected_total_items, assert len(result_limited) ==
expected_limited_items, and assert mock_get.call_count == expected_get_calls;
keep references to mock_get, get_news_data, and OWASP_NEWS_URL unchanged.

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 (5)
backend/tests/apps/owasp/index/registry/committee_test.py (1)

9-19: Consider merging the two with blocks into one.

The index is constructed in a separate context from where get_entities() is exercised. While functionally correct, the split is harder to follow at a glance and deviates from the nested/combined style used elsewhere in this PR (e.g., chapter_test.py). A single compound with keeps all test state within one scope.

♻️ Proposed refactor
-        with patch.object(CommitteeIndex, "__init__", lambda _: None):
-            index = CommitteeIndex()
-
-        with patch("apps.owasp.index.registry.committee.Committee") as mock_committee:
-            mock_manager = mock_committee.active_committees
-            mock_manager.select_related.return_value = ["committee1", "committee2"]
-
-            result = index.get_entities()
-
-            mock_manager.select_related.assert_called_once_with("owasp_repository")
-            assert result == ["committee1", "committee2"]
+        with (
+            patch.object(CommitteeIndex, "__init__", lambda _: None),
+            patch("apps.owasp.index.registry.committee.Committee") as mock_committee,
+        ):
+            index = CommitteeIndex()
+            mock_manager = mock_committee.active_committees
+            mock_manager.select_related.return_value = ["committee1", "committee2"]
+
+            result = index.get_entities()
+
+            mock_manager.select_related.assert_called_once_with("owasp_repository")
+            assert result == ["committee1", "committee2"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/owasp/index/registry/committee_test.py` around lines 9 -
19, Merge the two context managers into a single compound with to keep test
setup and execution in one scope: patch CommitteeIndex.__init__ (lambda _: None)
and patch "apps.owasp.index.registry.committee.Committee" together, then
instantiate CommitteeIndex, set
mock_committee.active_committees.select_related.return_value to
["committee1","committee2"], call index.get_entities(), and assert
mock_manager.select_related was called with "owasp_repository" and the result
equals the expected list; reference CommitteeIndex, CommitteeIndex.__init__,
mock_committee (Committee), get_entities, and active_committees.select_related
to locate the code to change.
backend/tests/apps/slack/management/commands/slack_sync_messages_test.py (4)

752-900: Consider extracting repeated workspace/client mock setup into a shared fixture.

Every test method in TestSyncUserMessagesMissingBranches (and several other new classes) duplicates the same ~15-line block for mocking Workspace, WebClient, and related queryset wiring. A @pytest.fixture or a helper method on the class would eliminate this boilerplate and make each test focus on what it actually varies.

♻️ Example fixture extraction
import pytest

`@pytest.fixture`()
def sync_command_env(mocker):
    """Common setup for _sync_user_messages tests."""
    target = "apps.slack.management.commands.slack_sync_messages"
    mocker.patch(f"{target}.os.environ.get", return_value="xoxp-token")
    mocker.patch(f"{target}.time.sleep")

    mock_workspace = mocker.patch(f"{target}.Workspace")
    mock_ws = MagicMock(name="Test")
    mock_qs = MagicMock()
    mock_qs.exists.return_value = True
    mock_qs.__iter__.return_value = [mock_ws]
    mock_workspace.objects.all.return_value = mock_qs

    mock_webclient = mocker.patch(f"{target}.WebClient")
    mock_client = MagicMock()
    mock_webclient.return_value = mock_client

    command = Command()
    command.stdout = MagicMock()

    return command, mock_client, mocker.patch(f"{target}.Conversation"), mocker.patch(f"{target}.Message")

Each test then only sets up its unique search_messages.side_effect and assertions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/slack/management/commands/slack_sync_messages_test.py`
around lines 752 - 900, Extract the repeated Workspace/WebClient/test command
setup used across TestSyncUserMessagesMissingBranches into a shared pytest
fixture (e.g., sync_command_env) or a class helper; move the block that patches
target_module.os.environ.get, time.sleep, patches for Workspace (including
mock_ws_instance/mock_qs wiring), WebClient (returning mock_client), and
instantiation of Command/command.stdout into that fixture/helper, return the
Command instance and mock_client and any patched Conversation/Message mocks so
tests can just set mock_client.search_messages.side_effect and call
command._sync_user_messages; update tests to accept/use the fixture (or call the
helper) and remove the duplicated setup code from each test.

908-934: Unnecessary Message mock on line 910.

_fetch_messages is patched to raise immediately, so the Message mock is never accessed. You can safely remove the mocker.patch(f"{self.target_module}.Message") line to reduce noise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/slack/management/commands/slack_sync_messages_test.py`
around lines 908 - 934, Remove the unnecessary mock of Message in the test: the
line mocking f"{self.target_module}.Message" should be deleted because
Command._fetch_messages is patched to raise a SlackApiError immediately and no
code path uses Message during this test; update the test method
test_fetch_conversation_api_error to rely only on the patched
Command._fetch_messages and keep assertions that command.stdout.write was called
after invoking Command._fetch_conversation with the mocked client and
conversation.

794-831: Weak assertion — stdout.write.assert_called() doesn't verify the None-message skip path.

stdout.write is invoked many times during _sync_user_messages (syncing header, workspace processing, etc.), so assert_called() will pass even if the _create_message returns None branch is never reached. Consider asserting the specific skip message was written:

💡 Suggested stronger assertion
         command._sync_user_messages("U123", "2024-01-01", "2024-01-02", 0.1, 3)
-        command.stdout.write.assert_called()
+        skip_calls = [
+            c for c in command.stdout.write.call_args_list
+            if "Skipped message" in str(c)
+        ]
+        assert len(skip_calls) == 1
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/slack/management/commands/slack_sync_messages_test.py`
around lines 794 - 831, The test currently only checks stdout.write was called
generally; update it to assert the specific skip log when _create_message
returns None by checking command.stdout.write was called with a string
containing the skip indicator and the message timestamp from the mocked search
(e.g., ensure command.stdout.write.assert_any_call(...) is used with a value
that includes "Skipping" and "1704110400"); locate this assertion around the
Command() instantiation and _sync_user_messages call, referencing
Command._sync_user_messages and Command._create_message to validate the
None-path was exercised.

873-900: Same weak assertion as test_sync_message_create_returns_none.

command.stdout.write.assert_called() will always pass since the method writes to stdout unconditionally at the start. Consider asserting the specific error message substring (e.g., "fatal_error") appears in one of the write calls to confirm the error handling branch was actually reached.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/slack/management/commands/slack_sync_messages_test.py`
around lines 873 - 900, Replace the weak assertion that only checks that
command.stdout.write was called with an assertion that the Slack error text was
actually logged by the _sync_user_messages path: after invoking
Command()._sync_user_messages, inspect command.stdout.write.call_args_list (or
mock_calls) and assert that at least one call's first positional argument
contains the substring "fatal_error" (e.g., assert any("fatal_error" in
call.args[0] for call in command.stdout.write.call_args_list)), so the test
verifies the error-handling branch in Command._sync_user_messages.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/tests/apps/slack/apps_test.py`:
- Around line 39-44: Replace the fragile importlib.reload approach in
_restore_module() with a safe sys.modules snapshot/restore: before tests mutate
apps.slack.apps save original_module = sys.modules.get('apps.slack.apps'), and
in the finally restore by setting sys.modules['apps.slack.apps'] =
original_module if original_module is not None else del
sys.modules['apps.slack.apps'] (or pop) so the module is returned to its prior
state without re-executing module init; update any tests that call
_restore_module() in try/finally blocks to avoid swallowing the original
exception (do not catch exceptions in finally or re-raise the original
assertion) so failures propagate.

---

Duplicate comments:
In `@backend/tests/apps/slack/management/commands/slack_sync_messages_test.py`:
- Around line 757-792: The review comment contains a duplicate marker
([duplicate_comment]) even though the test test_sync_message_no_channel_id
already adds the assertion that Conversation.objects.get_or_create isn’t called;
remove the redundant duplicate tag from the review/comment so the approval is
clear and not repeated—locate the test test_sync_message_no_channel_id (which
exercises Command._sync_user_messages and mocks mock_client.search_messages) and
update the PR comment to keep only the [approve_code_changes] marker.

---

Nitpick comments:
In `@backend/tests/apps/owasp/index/registry/committee_test.py`:
- Around line 9-19: Merge the two context managers into a single compound with
to keep test setup and execution in one scope: patch CommitteeIndex.__init__
(lambda _: None) and patch "apps.owasp.index.registry.committee.Committee"
together, then instantiate CommitteeIndex, set
mock_committee.active_committees.select_related.return_value to
["committee1","committee2"], call index.get_entities(), and assert
mock_manager.select_related was called with "owasp_repository" and the result
equals the expected list; reference CommitteeIndex, CommitteeIndex.__init__,
mock_committee (Committee), get_entities, and active_committees.select_related
to locate the code to change.

In `@backend/tests/apps/slack/management/commands/slack_sync_messages_test.py`:
- Around line 752-900: Extract the repeated Workspace/WebClient/test command
setup used across TestSyncUserMessagesMissingBranches into a shared pytest
fixture (e.g., sync_command_env) or a class helper; move the block that patches
target_module.os.environ.get, time.sleep, patches for Workspace (including
mock_ws_instance/mock_qs wiring), WebClient (returning mock_client), and
instantiation of Command/command.stdout into that fixture/helper, return the
Command instance and mock_client and any patched Conversation/Message mocks so
tests can just set mock_client.search_messages.side_effect and call
command._sync_user_messages; update tests to accept/use the fixture (or call the
helper) and remove the duplicated setup code from each test.
- Around line 908-934: Remove the unnecessary mock of Message in the test: the
line mocking f"{self.target_module}.Message" should be deleted because
Command._fetch_messages is patched to raise a SlackApiError immediately and no
code path uses Message during this test; update the test method
test_fetch_conversation_api_error to rely only on the patched
Command._fetch_messages and keep assertions that command.stdout.write was called
after invoking Command._fetch_conversation with the mocked client and
conversation.
- Around line 794-831: The test currently only checks stdout.write was called
generally; update it to assert the specific skip log when _create_message
returns None by checking command.stdout.write was called with a string
containing the skip indicator and the message timestamp from the mocked search
(e.g., ensure command.stdout.write.assert_any_call(...) is used with a value
that includes "Skipping" and "1704110400"); locate this assertion around the
Command() instantiation and _sync_user_messages call, referencing
Command._sync_user_messages and Command._create_message to validate the
None-path was exercised.
- Around line 873-900: Replace the weak assertion that only checks that
command.stdout.write was called with an assertion that the Slack error text was
actually logged by the _sync_user_messages path: after invoking
Command()._sync_user_messages, inspect command.stdout.write.call_args_list (or
mock_calls) and assert that at least one call's first positional argument
contains the substring "fatal_error" (e.g., assert any("fatal_error" in
call.args[0] for call in command.stdout.write.call_args_list)), so the test
verifies the error-handling branch in Command._sync_user_messages.

@HarshitVerma109
Copy link
Contributor Author

@cubic-dev-ai review this pull request

@cubic-dev-ai
Copy link
Contributor

cubic-dev-ai bot commented Feb 18, 2026

@cubic-dev-ai review this pull request

@HarshitVerma109 I have started the AI code review. It will take a few minutes to complete.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/tests/apps/slack/apps_test.py`:
- Around line 87-105: The test test_log_events_handles_exception accesses
handlers["use"] without first asserting the key exists; add a guard assertion
consistent with the other test by calling assert "use" in handlers immediately
after handlers, _ = self._reload_with_mock_app() (or after handlers are
assigned) so the test fails with a clear message if the handler capture changes;
update the test_log_events_handles_exception function to include this assert
before assigning log_events = handlers["use"].

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 33 files

Confidence score: 5/5

  • This PR looks safe to merge; the only noted issue is a low-severity test maintainability concern rather than runtime behavior.
  • Duplicated MagicMock queryset setup in backend/tests/apps/owasp/management/commands/owasp_enrich_chapters_test.py could make tests brittle if the mock needs updates in multiple places.
  • Pay close attention to backend/tests/apps/owasp/management/commands/owasp_enrich_chapters_test.py - duplicated mock setup should be refactored into shared setup to ease maintenance.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/tests/apps/owasp/management/commands/owasp_enrich_chapters_test.py">

<violation number="1" location="backend/tests/apps/owasp/management/commands/owasp_enrich_chapters_test.py:118">
P3: Duplicated complex queryset mock setup in test_handle_geo_exception repeats the same MagicMock configuration already used in test_handle. This makes the tests brittle to changes and should be extracted into a shared fixture/helper.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 18, 2026
@HarshitVerma109
Copy link
Contributor Author

@cubic-dev-ai review this pull request

@cubic-dev-ai
Copy link
Contributor

cubic-dev-ai bot commented Feb 18, 2026

@cubic-dev-ai review this pull request

@HarshitVerma109 I have started the AI code review. It will take a few minutes to complete.

cubic-dev-ai[bot]
cubic-dev-ai bot previously approved these changes Feb 18, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 33 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

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

Please update tests to be consistently grouped under classes (the inconsistency existed before your changes).

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 (1)
backend/tests/apps/owasp/management/commands/owasp_enrich_chapters_test.py (1)

104-133: test_handle_geo_exception is missing assertions for pre-exception steps and chapter state

The test only asserts that bulk_save and generate_geo_location were called, but doesn't verify:

  • That generate_summary and generate_suggested_location were actually invoked before the exception.
  • That chapter.latitude and chapter.longitude remain None after the exception path (confirming no partial write).

Consider adding:

         mock_bulk_save.assert_called()
         mock_chapter.generate_geo_location.assert_called_once()
+        mock_chapter.generate_summary.assert_called_once()
+        mock_chapter.generate_suggested_location.assert_called_once()
+        assert mock_chapter.latitude is None
+        assert mock_chapter.longitude is None

Also, if production code catches a specific exception type (e.g. requests.RequestException), using the broad Exception here won't catch a regression where that specific handling is removed. Consider using the same exception type that the production try/except targets.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/owasp/management/commands/owasp_enrich_chapters_test.py`
around lines 104 - 133, Update test_handle_geo_exception to assert pre-exception
steps and post-exception state: assert
mock_chapter.generate_summary.assert_called_once() and
mock_chapter.generate_suggested_location.assert_called_once() to verify those
were invoked, and assert that the chapter's latitude and longitude remain None
(e.g., check mock_chapter.latitude is None and mock_chapter.longitude is None)
to ensure no partial writes; also change the side_effect for
generate_geo_location from raising a bare Exception to raising the same specific
exception type the production code catches (e.g., requests.RequestException) so
the test matches production error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/tests/apps/owasp/management/commands/owasp_enrich_chapters_test.py`:
- Line 39: The test sets mock_active_chapters.__iter__.return_value to
iter(chapters_list) which produces a one-shot iterator that will be exhausted on
subsequent iterations; change the mock to provide a fresh iterator each time by
assigning mock_active_chapters.__iter__.side_effect = lambda:
iter(chapters_list) (or an equivalent callable) so every call to
iter(mock_active_chapters) yields a new iterator for use in loops inside the
command.

---

Nitpick comments:
In `@backend/tests/apps/owasp/management/commands/owasp_enrich_chapters_test.py`:
- Around line 104-133: Update test_handle_geo_exception to assert pre-exception
steps and post-exception state: assert
mock_chapter.generate_summary.assert_called_once() and
mock_chapter.generate_suggested_location.assert_called_once() to verify those
were invoked, and assert that the chapter's latitude and longitude remain None
(e.g., check mock_chapter.latitude is None and mock_chapter.longitude is None)
to ensure no partial writes; also change the side_effect for
generate_geo_location from raising a bare Exception to raising the same specific
exception type the production code catches (e.g., requests.RequestException) so
the test matches production error handling.

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

🧹 Nitpick comments (18)
backend/tests/apps/api/decorators/cache_test.py (1)

13-30: LGTM — missing class docstring (nit).

The refactor correctly encapsulates the previously module-level test_generate_cache_key into TestGenerateCacheKey, aligning with the class-based style used by TestCacheResponse. All four parameterized cases are logically correct:

  • The path-only case verifies no stray ? is appended for an empty query string.
  • The ?b=2&a=1 case is a good explicit guard against any accidental query-parameter sorting in generate_key.

One minor nit: TestGenerateCacheKey is missing a class-level docstring, which TestCacheResponse has at line 34.

📝 Optional: add class docstring for consistency
 class TestGenerateCacheKey:
+    """Test cases for the generate_key cache-key helper."""
+
     `@pytest.mark.parametrize`(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/api/decorators/cache_test.py` around lines 13 - 30, Add a
class-level docstring to TestGenerateCacheKey to match the style of
TestCacheResponse; open the TestGenerateCacheKey class definition and insert a
concise docstring that summarizes the purpose (e.g., "Tests for generate_key
cache key generation") immediately after the class line so that the class has
the same documentation pattern as other test classes; ensure the docstring is a
proper triple-quoted string placed before any methods like
test_generate_cache_key and uses clear, short wording.
backend/tests/apps/github/models/mixins/release_test.py (2)

61-61: Nit: prefer explicit == [] over not expected for the empty-author guard.

not expected is falsy for [], None, 0, False, etc. While only [] is reachable in this parametrize set, the intent is clearer with an explicit comparison.

♻️ Proposed fix
-        if attr == "idx_author" and not expected:
+        if attr == "idx_author" and expected == []:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/github/models/mixins/release_test.py` at line 61, Replace
the falsy check for empty authors with an explicit empty-list comparison: in the
test where the conditional reads "if attr == 'idx_author' and not expected",
change it to check "expected == []" so the guard only triggers for an actual
empty list; locate the conditional using the variables attr, expected and the
literal 'idx_author' in the release_test.py test and update that expression
accordingly.

52-53: idx_repository test doesn't exercise the .lower() call.

The fixture path "mock/repository" is already all-lowercase, so self.repository.path.lower() is a no-op and the lowercasing logic in idx_repository is never actually verified. Consider feeding a mixed-case path in a dedicated parametrize case.

🔍 Proposed addition to the parametrize list
         ("idx_repository", "mock/repository"),
+        ("idx_repository", "mock/repository"),  # lowercase transform tested below

Alternatively, update the fixture path and the expected value in the existing case:

-            ("idx_repository", "mock/repository"),
+            ("idx_repository", "mock/repository"),  # already covers exact match

A cleaner option is to add a dedicated entry that exercises .lower():

In the parametrize list add:

         ("idx_tag_name", "v1.0.0"),
+        ("idx_repository", "mock/repository"),  # path already lowercase; no transform

And in the fixture or a dedicated branch, supply an uppercase path:

     elif attr == "idx_published_at" and expected is None:
         release_index_mixin_instance.published_at = None
+    elif attr == "idx_repository" and expected == "mock/repository" and ...:
+        release_index_mixin_instance.repository = MagicMock(
+            path="Mock/Repository",
+            project=MagicMock(nest_key="mock/project"),
+        )

Or simply change the existing fixture's path to "Mock/Repository" and update the expected value to "mock/repository" so .lower() is actually exercised.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/github/models/mixins/release_test.py` around lines 52 -
53, The test currently uses the all-lowercase value "mock/repository" for the
"idx_repository" case so the lowercasing in idx_repository (which calls
self.repository.path.lower()) is not exercised; update the parametrize set in
backend/tests/apps/github/models/mixins/release_test.py to include a case with a
mixed-case repository path (e.g., "Mock/Repository") and set the expected
"idx_repository" value to the lowercase form ("mock/repository"), or replace the
existing fixture path with a mixed-case value and update the expected tuple
accordingly so the .lower() behavior in idx_repository is actually verified.
backend/tests/apps/owasp/index/search/chapter_test.py (1)

17-29: Use integer page values to match sibling tests.

The page parametrize values are strings ("1", "2") while issue_test.py and project_test.py use plain integers. The int(page) cast at the call site is an unnecessary indirection that adds noise without exercising any coercion path in get_chapters.

♻️ Proposed fix
     `@pytest.mark.parametrize`(
         ("query", "page", "expected_hits"),
         [
-            ("security", "1", MOCKED_HITS),
-            ("web", "2", MOCKED_HITS),
-            ("", "1", MOCKED_HITS),
+            ("security", 1, MOCKED_HITS),
+            ("web", 2, MOCKED_HITS),
+            ("", 1, MOCKED_HITS),
         ],
     )
     def test_get_chapters(self, query, page, expected_hits):
         with patch(
             "apps.owasp.index.search.chapter.raw_search", return_value=expected_hits
         ) as mock_raw_search:
-            result = get_chapters(query=query, page=int(page))
+            result = get_chapters(query=query, page=page)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/owasp/index/search/chapter_test.py` around lines 17 - 29,
The test uses string page values and then calls get_chapters with int(page);
update the pytest.mark.parametrize tuple for ("query", "page", "expected_hits")
to use integer page values (e.g., 1, 2) instead of "1", "2" and then call
get_chapters(query=query, page=page) in test_get_chapters; this keeps the test
consistent with sibling tests and removes the unnecessary int(...) coercion
while still exercising get_chapters.
backend/tests/apps/slack/commands/users_test.py (2)

75-92: test_users_handler_with_blocks omits chat_postMessage call-count assertion.

The test verifies the content of chat_postMessage's blocks argument but never asserts that chat_postMessage was actually called exactly once. If the handler skips posting (e.g., due to an exception or a falsy blocks return), call_args would be None and the assertions on lines 89–92 would raise TypeError rather than a clean failure, masking the real problem.

♻️ Proposed fix
         ack.assert_called_once()
 
         mock_client.conversations_open.assert_called_once_with(users="U123456")
+        assert mock_client.chat_postMessage.call_count == 1
         blocks = mock_client.chat_postMessage.call_args[1]["blocks"]
         assert len(blocks) == len(mock_get_blocks.return_value)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/slack/commands/users_test.py` around lines 75 - 92, The
test_users_handler_with_blocks is missing an explicit assertion that
mock_client.chat_postMessage was called; update the test
(test_users_handler_with_blocks) to assert
mock_client.chat_postMessage.assert_called_once() (or
assert_called_once_with(...) if you prefer) immediately after invoking
Users().handler(ack=ack, command=mock_command, client=mock_client) and before
accessing mock_client.chat_postMessage.call_args, so the test fails clearly if
the handler did not post a message.

52-54: Remove duplicate ack.assert_called_once() assertion.

Line 54 is an exact duplicate of line 52; the second call is redundant.

🧹 Proposed fix
         ack.assert_called_once()
 
-        ack.assert_called_once()
         assert mock_client.chat_postMessage.call_count == expected_calls
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/slack/commands/users_test.py` around lines 52 - 54, Remove
the duplicate assertion call by deleting the second ack.assert_called_once() so
the test only asserts ack.assert_called_once() once; locate the duplicate in
backend/tests/apps/slack/commands/users_test.py (the repeated
ack.assert_called_once() lines) and keep a single assertion to avoid redundancy.
backend/tests/apps/api/rest/v0/structured_search_test.py (2)

91-101: Missing positive assertion for the exact lookup suffix.

The test only checks that name__icontains is absent (line 101), but it never verifies that name__exact is actually present. If the filter were applied with a different (or empty) lookup, the test would still pass, leaving the intended behavior unvalidated.

♻️ Suggested improvement
         qs.filter.assert_called_once()
         args, _ = qs.filter.call_args
         assert "name__icontains" not in str(args[0])
+        assert "name__exact" in str(args[0])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/api/rest/v0/structured_search_test.py` around lines 91 -
101, The test test_string_field_with_exact_lookup currently only asserts that
"name__icontains" is absent; update it to also assert that the filter call
includes the exact lookup by checking that "name__exact" appears in the filter
args after apply_structured_search; locate the variables schema_with_exact, qs
(make_queryset()), the call apply_structured_search(qs, "name:test",
schema_with_exact) and the mocked qs.filter call, and add an assertion that
"name__exact" is present in str(args[0]) (or equivalent) to confirm the exact
lookup was applied.

140-151: Weak assertion — doesn't verify the unknown field was excluded from the Q object.

filter.assert_called_once() only confirms the function reached the filter call without erroring. It does not verify that the unrecognised field "query" was actually skipped (i.e., that the resulting Q object is empty). A poorly behaved change could include the unknown field in the filter and this test would still pass.

♻️ Suggested improvement
             apply_structured_search(qs, "test", FIELD_SCHEMA)

         qs.filter.assert_called_once()
+        args, _ = qs.filter.call_args
+        assert "query" not in str(args[0])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/api/rest/v0/structured_search_test.py` around lines 140 -
151, Update the test_condition_field_not_in_schema_is_skipped test to assert the
actual Q object passed to qs.filter is empty rather than just that filter was
called: after calling apply_structured_search(qs, "test", FIELD_SCHEMA) fetch
qs.filter.call_args (or call_args_list[0]) and assert the first positional
argument is an empty django.db.models.Q() (or that its .children is empty),
using Q from django.db.models so you verify apply_structured_search (and
QueryParser mock) skipped the unknown "query" field.
backend/tests/apps/github/management/commands/github_update_owasp_organization_test.py (1)

103-114: PaginatedListMock is duplicated across two test methods.

The identical helper class appears in both test_handle (lines 103–114) and test_handle_full_sync_with_errors_and_repo_linking (lines 195–206). Extract it to the class or module level to reduce duplication.

Also applies to: 195-206

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/tests/apps/github/management/commands/github_update_owasp_organization_test.py`
around lines 103 - 114, The PaginatedListMock helper class is duplicated in
test_handle and test_handle_full_sync_with_errors_and_repo_linking; extract
PaginatedListMock to the module/class scope so both tests reuse the same
definition. Move the class definition currently inside test_handle (and remove
the duplicate inside test_handle_full_sync_with_errors_and_repo_linking),
placing a single PaginatedListMock definition at the top of the test module or
test class so both tests reference that symbol.
backend/tests/apps/github/management/commands/github_add_related_repositories_test.py (2)

67-77: Repeated active_projects mock setup across tests.

The pattern of configuring __iter__, count, __getitem__, and order_by on the active_projects mock is repeated nearly identically in every test method. Consider extracting a helper (e.g., _setup_active_projects(self, projects_list)) on the test class to reduce boilerplate, similar to the _setup_organizations_mock pattern in github_update_related_organizations_test.py.

Also applies to: 118-123, 171-176, 216-221, 270-274, 316-320

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/tests/apps/github/management/commands/github_add_related_repositories_test.py`
around lines 67 - 77, The repeated setup of mock_active_projects (setting
__iter__, count, __getitem__, and order_by) should be extracted into a reusable
helper on the test class (e.g., _setup_active_projects(self, projects_list)) to
remove boilerplate; implement _setup_active_projects to create/configure and
return a MagicMock with __iter__.return_value = iter(projects_list),
count.return_value = len(projects_list), a __getitem__ lambda handling int vs
slice, and order_by.return_value = the mock itself, then replace each in-test
block that constructs mock_active_projects with calls to this helper (update
tests at the multiple locations noted).

117-149: Missing command.stdout mock in several test methods.

test_handle (line 83) sets command.stdout = mock.MagicMock() to suppress output, but test_handle_unknown_object_exception, test_handle_no_repository_path, test_handle_unknown_object_exception_non_404, test_handle_sync_repository_exception, and test_handle_sync_repository_returns_none_organization do not. If the command writes to stdout in these paths, the tests will emit output to the console during test runs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/tests/apps/github/management/commands/github_add_related_repositories_test.py`
around lines 117 - 149, Add a mock for command.stdout in the specified test
methods so test output is suppressed: in test_handle_unknown_object_exception,
test_handle_no_repository_path, test_handle_unknown_object_exception_non_404,
test_handle_sync_repository_exception, and
test_handle_sync_repository_returns_none_organization set command.stdout =
mock.MagicMock() (same as test_handle does) before invoking command.handle; this
ensures any writes during command.handle (and related calls like
Project.bulk_save, mock_gh_client.get_repo, mock_sync_repository) do not print
to the console during test runs.
backend/tests/apps/github/management/commands/github_enrich_issues_test.py (1)

177-209: Misleading test name: test_handle_with_chunked_save doesn't test chunking.

The test creates 50 issues and asserts bulk_save is called exactly once with all 50 items (lines 205–209). This validates single-batch processing, not chunked saving. The docstring ("correctly processes multiple issues") is more accurate than the method name.

Consider renaming to test_handle_processes_all_issues or similar.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/github/management/commands/github_enrich_issues_test.py`
around lines 177 - 209, The test method name test_handle_with_chunked_save is
misleading because it does not verify chunked saves; rename the test to
something like test_handle_processes_all_issues (and update the docstring to
match) so the name reflects that it asserts a single bulk_save call with all 50
issues; locate the test function definition in github_enrich_issues_test.py,
change the method name and any internal references (e.g., any test runner/tests
mapping) and keep the existing assertions around mock_issue_class.bulk_save and
kwargs["fields"] unchanged.
backend/tests/apps/api/rest/v0/chapter_test.py (1)

37-54: MockMember / MockEntityMember duplicated in project_test.py.

These two helper classes (lines 37-44) are identical to the ones defined inside test_project_serializer_validation in project_test.py (lines 33-40). If either evolves (e.g., the resolver adds a field), both copies need updating. Consider extracting them to a shared conftest.py fixture or a module-level definition.

♻️ Suggested extraction to conftest.py
# In backend/tests/apps/api/rest/v0/conftest.py (new or existing)
import pytest

class MockMember:
    def __init__(self, login):
        self.login = login

class MockEntityMember:
    def __init__(self, name, login=None):
        self.member = MockMember(login) if login else None
        self.member_name = name

`@pytest.fixture`
def make_entity_member():
    return MockEntityMember
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/api/rest/v0/chapter_test.py` around lines 37 - 54, The
MockMember and MockEntityMember classes are duplicated between chapter_test.py
and project_test.py; extract them to a shared test location (e.g.,
backend/tests/apps/api/rest/v0/conftest.py or a module-level helpers file) and
import or reference the shared definitions from both test files so updates are
centralized; specifically move the MockMember and MockEntityMember
implementations out of test functions into the shared conftest/module and change
chapter_test.py and project_test.py to use the shared
MockMember/MockEntityMember (or a fixture like make_entity_member) instead of
their local copies.
backend/tests/apps/api/rest/v0/common_test.py (1)

7-30: Consider adding a test case for None defaults.

Both parametrize cases supply all four fields with float values. LocationFilter declares all fields as float | None = Field(None, ...), so the all-None default path is untested. Adding a third case (empty dict {}) would cover that branch.

♻️ Suggested additional test case
         {
             "latitude_gte": 15.0,
             "latitude_lte": 25.0,
             "longitude_gte": 20.0,
             "longitude_lte": 30.0,
         },
+        {
+            "latitude_gte": None,
+            "latitude_lte": None,
+            "longitude_gte": None,
+            "longitude_lte": None,
+        },
     ],
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/api/rest/v0/common_test.py` around lines 7 - 30, Add a
third parametrize case to test the None/default path: update the
test_location_filter_validation parameter list to include an empty dict {} as a
third location_data case, then assert that LocationFilter(**location_data)
yields latitude_gte, latitude_lte, longitude_gte, and longitude_lte all equal to
None for that case; reference the existing test function
test_location_filter_validation and the LocationFilter model to locate where to
add this case.
backend/tests/apps/api/rest/v0/committee_test.py (1)

10-10: Missing class-level docstring — inconsistent with existing test classes.

TestListCommittees (line 47) and TestGetCommittee (line 75) both have docstrings, as do their counterparts in the other test files. The new class TestCommitteeSerializerValidation (and its equivalents in chapter_test.py, common_test.py, project_test.py, event_test.py) lack docstrings. Consider adding one for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/api/rest/v0/committee_test.py` at line 10, Add a
class-level docstring to the TestCommitteeSerializerValidation test class to
match the style used by TestListCommittees and TestGetCommittee; locate the
class named TestCommitteeSerializerValidation and add a concise docstring
describing the purpose (e.g., "Tests for validating Committee serializer input
and error responses"), and apply the same change to the equivalent classes in
chapter_test.py, common_test.py, project_test.py, and event_test.py to keep
test-file docstring consistency.
backend/tests/apps/owasp/management/commands/owasp_enrich_chapters_test.py (1)

104-133: Consider asserting that latitude/longitude remain None after a geo exception.

The test correctly verifies that bulk_save is still invoked and that generate_geo_location was called. An additional assertion that the chapter's geo fields were not mutated would make the exception-path contract explicit and guard against future regressions where a partial update slips through.

✨ Suggested addition
         mock_bulk_save.assert_called()
         mock_chapter.generate_geo_location.assert_called_once()
+        assert mock_chapter.latitude is None
+        assert mock_chapter.longitude is None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/owasp/management/commands/owasp_enrich_chapters_test.py`
around lines 104 - 133, In test_handle_geo_exception, add assertions after
command.handle to verify that the chapter's geo fields were not mutated when
generate_geo_location raised an exception: assert mock_chapter.latitude is None
and assert mock_chapter.longitude is None (or the model's equivalent field
names) so the test ensures Chapter instances retain None latitude/longitude
after the geo exception; keep these assertions using the existing mock_chapter
used in the test and place them just before the final mock_bulk_save and
generate_geo_location assertions or immediately after command.handle.
backend/tests/apps/slack/utils_test.py (2)

336-343: Missing test for yaml.scanner.ScannerError error path.

get_staff_data catches both RequestException and yaml.scanner.ScannerError, but only the former is exercised. If the goal of this PR is 100% branch coverage, the YAML parsing error branch is not covered. Consider adding:

➕ Suggested additional test
def test_get_staff_data_yaml_error(self, monkeypatch):
    """Test get_staff_data handles YAML parsing errors gracefully."""
    import yaml

    mock_response = Mock()
    mock_response.text = ": invalid: yaml: ["
    mock_get = Mock(return_value=mock_response)
    monkeypatch.setattr("requests.get", mock_get)
    get_staff_data.cache_clear()

    result = get_staff_data()
    assert result is None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/slack/utils_test.py` around lines 336 - 343, Add a new
unit test covering the YAML parsing error path for get_staff_data: mock
requests.get to return a response-like object whose .text is invalid YAML (so
yaml.safe_load raises yaml.scanner.ScannerError), call
get_staff_data.cache_clear(), invoke get_staff_data() and assert it returns
None; reference the existing test_get_staff_data_request_exception pattern and
use get_staff_data and requests.get when locating where to add the new test.

275-287: length is reused for three different semantics — clarify the HTTP call count assertion.

length is first set to 3 (news item count), then reassigned to 2 (limit), and then silently reused for the HTTP call-count assertion at line 287. Because the limit and expected call count happen to share the value 2, the assertion appears to check "2 results" rather than "exactly 2 HTTP requests", which is the actual intent.

♻️ Proposed refactor
-    length = 2
     result_limited = get_news_data(limit=2)
-    assert len(result_limited) == length
+    assert len(result_limited) == 2

     mock_get.assert_called_with(OWASP_NEWS_URL, timeout=30)
     result2 = get_news_data()
-    assert mock_get.call_count == length
+    assert mock_get.call_count == 2  # default (limit=10) + limited (limit=2); third call is a cache hit
     assert result == result2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/apps/slack/utils_test.py` around lines 275 - 287, The test
reuses the variable length for different meanings (3 items, 2 limit) and then
uses it to assert mock_get.call_count, making the intent unclear; update the
test around get_news_data, replacing reused length with clearly named variables
(e.g., expected_total_items, expected_limited_items, expected_http_calls) and
assert mock_get.call_count == expected_http_calls; reference get_news_data,
mock_get, and OWASP_NEWS_URL to locate and update the assertions so the HTTP
call-count check explicitly compares to the intended number of requests rather
than a reused length variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/tests/apps/api/rest/v0/structured_search_test.py`:
- Around line 42-49: The test named
test_invalid_syntax_returns_original_queryset is misleading because
apply_structured_search(qs, "stars:!!!", FIELD_SCHEMA) actually calls
qs.filter(Q()) after skipping the invalid numeric value; update the test to
reflect that behavior: rename the test to something like
test_invalid_number_skipped_applies_empty_filter, and change/assertions to
verify that qs.filter.assert_called_once() and that the returned value equals
qs.filter.return_value (i.e., capture result = apply_structured_search(...) and
assert result is qs.filter.return_value), while keeping the existing check that
"stars_count" is not in the filter args; refer to the test function name,
apply_structured_search, make_queryset, FIELD_SCHEMA, and qs.filter when making
these edits.

In
`@backend/tests/apps/github/management/commands/github_update_owasp_organization_test.py`:
- Around line 63-64: expected_calls for the full-sync (repository=None) cases
are never asserted; update the assertion block that currently only checks
expected_calls when repository_name is truthy to also verify the None branch:
iterate expected_calls entries where the first element is None and assert the
mocked update_data calls match those entries (either by checking
command.update_data.call_args_list contains calls with the expected (None, id,
counts) tuples or by asserting command.update_data.call_count/filtered calls
equal the expected counts), instead of only asserting
command.stdout.write.call_count > 0; use the existing symbols expected_calls,
repository_name, and command.update_data / command.stdout.write to locate and
implement the checks.

In
`@backend/tests/apps/github/management/commands/github_update_related_organizations_test.py`:
- Around line 80-85: Remove the dead pytest fixture named mock_get_github_client
from the test module; it is unused by TestGithubUpdateRelatedOrganizations and
duplicates the existing patch applied in the autouse setup fixture (which uses
monkeypatch.setattr to patch get_github_client). Delete the fixture definition
block (the function mock_get_github_client and its mock.patch context) so only
the autouse setup that patches get_github_client remains.

In `@backend/tests/apps/slack/commands/users_test.py`:
- Line 40: The tests mutate the global Django settings by assigning
settings.SLACK_COMMANDS_ENABLED directly, which leaks state; update the tests
(e.g., test_users_handler and test_users_handler_with_blocks) to accept the
pytest-django settings fixture and set settings.SLACK_COMMANDS_ENABLED =
commands_enabled using that fixture (or use
settings.setattr('SLACK_COMMANDS_ENABLED', commands_enabled)) so the change is
automatically restored after each test; remove any direct assignments to the
global settings object and ensure the fixture name is added to the test
signature where you adjust SLACK_COMMANDS_ENABLED.

In `@backend/tests/apps/slack/utils_test.py`:
- Around line 233-260: The test for get_gsoc_projects is missing a cache clear
which can cause stale results; before calling get_gsoc_projects("2023") (and
before the second call), clear the function cache by invoking
get_gsoc_projects.cache_clear() so the mocked get_projects (mock_get_projects)
is actually exercised and mock_get_projects.call_count remains deterministic;
modify the TestGetGsocProjects.test_get_gsoc_projects setup to call
get_gsoc_projects.cache_clear() prior to the first invocation.

---

Nitpick comments:
In `@backend/tests/apps/api/decorators/cache_test.py`:
- Around line 13-30: Add a class-level docstring to TestGenerateCacheKey to
match the style of TestCacheResponse; open the TestGenerateCacheKey class
definition and insert a concise docstring that summarizes the purpose (e.g.,
"Tests for generate_key cache key generation") immediately after the class line
so that the class has the same documentation pattern as other test classes;
ensure the docstring is a proper triple-quoted string placed before any methods
like test_generate_cache_key and uses clear, short wording.

In `@backend/tests/apps/api/rest/v0/chapter_test.py`:
- Around line 37-54: The MockMember and MockEntityMember classes are duplicated
between chapter_test.py and project_test.py; extract them to a shared test
location (e.g., backend/tests/apps/api/rest/v0/conftest.py or a module-level
helpers file) and import or reference the shared definitions from both test
files so updates are centralized; specifically move the MockMember and
MockEntityMember implementations out of test functions into the shared
conftest/module and change chapter_test.py and project_test.py to use the shared
MockMember/MockEntityMember (or a fixture like make_entity_member) instead of
their local copies.

In `@backend/tests/apps/api/rest/v0/committee_test.py`:
- Line 10: Add a class-level docstring to the TestCommitteeSerializerValidation
test class to match the style used by TestListCommittees and TestGetCommittee;
locate the class named TestCommitteeSerializerValidation and add a concise
docstring describing the purpose (e.g., "Tests for validating Committee
serializer input and error responses"), and apply the same change to the
equivalent classes in chapter_test.py, common_test.py, project_test.py, and
event_test.py to keep test-file docstring consistency.

In `@backend/tests/apps/api/rest/v0/common_test.py`:
- Around line 7-30: Add a third parametrize case to test the None/default path:
update the test_location_filter_validation parameter list to include an empty
dict {} as a third location_data case, then assert that
LocationFilter(**location_data) yields latitude_gte, latitude_lte,
longitude_gte, and longitude_lte all equal to None for that case; reference the
existing test function test_location_filter_validation and the LocationFilter
model to locate where to add this case.

In `@backend/tests/apps/api/rest/v0/structured_search_test.py`:
- Around line 91-101: The test test_string_field_with_exact_lookup currently
only asserts that "name__icontains" is absent; update it to also assert that the
filter call includes the exact lookup by checking that "name__exact" appears in
the filter args after apply_structured_search; locate the variables
schema_with_exact, qs (make_queryset()), the call apply_structured_search(qs,
"name:test", schema_with_exact) and the mocked qs.filter call, and add an
assertion that "name__exact" is present in str(args[0]) (or equivalent) to
confirm the exact lookup was applied.
- Around line 140-151: Update the test_condition_field_not_in_schema_is_skipped
test to assert the actual Q object passed to qs.filter is empty rather than just
that filter was called: after calling apply_structured_search(qs, "test",
FIELD_SCHEMA) fetch qs.filter.call_args (or call_args_list[0]) and assert the
first positional argument is an empty django.db.models.Q() (or that its
.children is empty), using Q from django.db.models so you verify
apply_structured_search (and QueryParser mock) skipped the unknown "query"
field.

In
`@backend/tests/apps/github/management/commands/github_add_related_repositories_test.py`:
- Around line 67-77: The repeated setup of mock_active_projects (setting
__iter__, count, __getitem__, and order_by) should be extracted into a reusable
helper on the test class (e.g., _setup_active_projects(self, projects_list)) to
remove boilerplate; implement _setup_active_projects to create/configure and
return a MagicMock with __iter__.return_value = iter(projects_list),
count.return_value = len(projects_list), a __getitem__ lambda handling int vs
slice, and order_by.return_value = the mock itself, then replace each in-test
block that constructs mock_active_projects with calls to this helper (update
tests at the multiple locations noted).
- Around line 117-149: Add a mock for command.stdout in the specified test
methods so test output is suppressed: in test_handle_unknown_object_exception,
test_handle_no_repository_path, test_handle_unknown_object_exception_non_404,
test_handle_sync_repository_exception, and
test_handle_sync_repository_returns_none_organization set command.stdout =
mock.MagicMock() (same as test_handle does) before invoking command.handle; this
ensures any writes during command.handle (and related calls like
Project.bulk_save, mock_gh_client.get_repo, mock_sync_repository) do not print
to the console during test runs.

In `@backend/tests/apps/github/management/commands/github_enrich_issues_test.py`:
- Around line 177-209: The test method name test_handle_with_chunked_save is
misleading because it does not verify chunked saves; rename the test to
something like test_handle_processes_all_issues (and update the docstring to
match) so the name reflects that it asserts a single bulk_save call with all 50
issues; locate the test function definition in github_enrich_issues_test.py,
change the method name and any internal references (e.g., any test runner/tests
mapping) and keep the existing assertions around mock_issue_class.bulk_save and
kwargs["fields"] unchanged.

In
`@backend/tests/apps/github/management/commands/github_update_owasp_organization_test.py`:
- Around line 103-114: The PaginatedListMock helper class is duplicated in
test_handle and test_handle_full_sync_with_errors_and_repo_linking; extract
PaginatedListMock to the module/class scope so both tests reuse the same
definition. Move the class definition currently inside test_handle (and remove
the duplicate inside test_handle_full_sync_with_errors_and_repo_linking),
placing a single PaginatedListMock definition at the top of the test module or
test class so both tests reference that symbol.

In `@backend/tests/apps/github/models/mixins/release_test.py`:
- Line 61: Replace the falsy check for empty authors with an explicit empty-list
comparison: in the test where the conditional reads "if attr == 'idx_author' and
not expected", change it to check "expected == []" so the guard only triggers
for an actual empty list; locate the conditional using the variables attr,
expected and the literal 'idx_author' in the release_test.py test and update
that expression accordingly.
- Around line 52-53: The test currently uses the all-lowercase value
"mock/repository" for the "idx_repository" case so the lowercasing in
idx_repository (which calls self.repository.path.lower()) is not exercised;
update the parametrize set in
backend/tests/apps/github/models/mixins/release_test.py to include a case with a
mixed-case repository path (e.g., "Mock/Repository") and set the expected
"idx_repository" value to the lowercase form ("mock/repository"), or replace the
existing fixture path with a mixed-case value and update the expected tuple
accordingly so the .lower() behavior in idx_repository is actually verified.

In `@backend/tests/apps/owasp/index/search/chapter_test.py`:
- Around line 17-29: The test uses string page values and then calls
get_chapters with int(page); update the pytest.mark.parametrize tuple for
("query", "page", "expected_hits") to use integer page values (e.g., 1, 2)
instead of "1", "2" and then call get_chapters(query=query, page=page) in
test_get_chapters; this keeps the test consistent with sibling tests and removes
the unnecessary int(...) coercion while still exercising get_chapters.

In `@backend/tests/apps/owasp/management/commands/owasp_enrich_chapters_test.py`:
- Around line 104-133: In test_handle_geo_exception, add assertions after
command.handle to verify that the chapter's geo fields were not mutated when
generate_geo_location raised an exception: assert mock_chapter.latitude is None
and assert mock_chapter.longitude is None (or the model's equivalent field
names) so the test ensures Chapter instances retain None latitude/longitude
after the geo exception; keep these assertions using the existing mock_chapter
used in the test and place them just before the final mock_bulk_save and
generate_geo_location assertions or immediately after command.handle.

In `@backend/tests/apps/slack/commands/users_test.py`:
- Around line 75-92: The test_users_handler_with_blocks is missing an explicit
assertion that mock_client.chat_postMessage was called; update the test
(test_users_handler_with_blocks) to assert
mock_client.chat_postMessage.assert_called_once() (or
assert_called_once_with(...) if you prefer) immediately after invoking
Users().handler(ack=ack, command=mock_command, client=mock_client) and before
accessing mock_client.chat_postMessage.call_args, so the test fails clearly if
the handler did not post a message.
- Around line 52-54: Remove the duplicate assertion call by deleting the second
ack.assert_called_once() so the test only asserts ack.assert_called_once() once;
locate the duplicate in backend/tests/apps/slack/commands/users_test.py (the
repeated ack.assert_called_once() lines) and keep a single assertion to avoid
redundancy.

In `@backend/tests/apps/slack/utils_test.py`:
- Around line 336-343: Add a new unit test covering the YAML parsing error path
for get_staff_data: mock requests.get to return a response-like object whose
.text is invalid YAML (so yaml.safe_load raises yaml.scanner.ScannerError), call
get_staff_data.cache_clear(), invoke get_staff_data() and assert it returns
None; reference the existing test_get_staff_data_request_exception pattern and
use get_staff_data and requests.get when locating where to add the new test.
- Around line 275-287: The test reuses the variable length for different
meanings (3 items, 2 limit) and then uses it to assert mock_get.call_count,
making the intent unclear; update the test around get_news_data, replacing
reused length with clearly named variables (e.g., expected_total_items,
expected_limited_items, expected_http_calls) and assert mock_get.call_count ==
expected_http_calls; reference get_news_data, mock_get, and OWASP_NEWS_URL to
locate and update the assertions so the HTTP call-count check explicitly
compares to the intended number of requests rather than a reused length
variable.

@HarshitVerma109
Copy link
Contributor Author

Hi @arkid15r, please review this and let me know if changes are needed. With this PR, backend coverage is officially at 100%, and I'm ready for the frontend next. I’ve created an issue #3959 ; feel free to assign it to me so I can hit that 100% mark there as well.

@arkid15r arkid15r enabled auto-merge February 18, 2026 20:16
@sonarqubecloud
Copy link

@codecov
Copy link

codecov bot commented Feb 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.79%. Comparing base (3208021) to head (61543d8).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3991      +/-   ##
==========================================
+ Coverage   99.15%   99.79%   +0.64%     
==========================================
  Files         517      517              
  Lines       15914    15914              
  Branches     2170     2168       -2     
==========================================
+ Hits        15779    15881     +102     
+ Misses         66        3      -63     
+ Partials       69       30      -39     
Flag Coverage Δ
backend 100.00% <100.00%> (+0.85%) ⬆️
frontend 99.16% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...apps/owasp/management/commands/owasp_sync_posts.py 100.00% <100.00%> (+2.63%) ⬆️
backend/apps/owasp/models/common.py 100.00% <100.00%> (+2.28%) ⬆️

... and 29 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3208021...61543d8. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

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

LGTM

Thanks for addressing this.

@arkid15r arkid15r added this pull request to the merge queue Feb 18, 2026
Merged via the queue into OWASP:main with commit 69b947b Feb 18, 2026
35 checks passed
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 Test Coverage to 100%

2 participants

Comments