-
-
Notifications
You must be signed in to change notification settings - Fork 313
Security Dashboard (Phase 1): test coverage #5315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
👋 Hi @Nachiket-Roy! This pull request needs a peer review before it can be merged. Please request a review from a team member who is not:
Once a valid peer review is submitted, this check will pass automatically. Thank you! |
WalkthroughAdds create/update hooks to the SecurityIncident API (force partial updates and attach reporter), normalizes severity/status input in the serializer, tweaks a template index access, and adds extensive tests for CSV export/sanitization, API, models, forms, and views. Changes
Sequence Diagram(s)sequenceDiagram
actor Client
participant ViewSet as SecurityIncidentViewSet
participant Serializer as SecurityIncidentSerializer
participant DB as Database
rect rgb(200,220,240)
Note over Client,ViewSet: Create (POST /api/v1/security-incidents/)
Client->>ViewSet: POST payload (auth)
ViewSet->>Serializer: to_internal_value / validate
Serializer->>Serializer: validate_severity()\nvalidate_status()
Serializer-->>ViewSet: validated_data
ViewSet->>ViewSet: perform_create(serializer)
ViewSet->>Serializer: serializer.save(reporter=request.user)
Serializer->>DB: INSERT SecurityIncident(...)
DB-->>Serializer: created instance
Serializer-->>ViewSet: instance
ViewSet-->>Client: 201 Created
end
rect rgb(240,200,200)
Note over Client,ViewSet: Update (PUT/PATCH /api/v1/security-incidents/{id}/)
Client->>ViewSet: PUT/PATCH payload (auth)
ViewSet->>ViewSet: update(...)\nsets partial=True
ViewSet->>Serializer: validate with partial=True
Serializer->>Serializer: validate_severity()\nvalidate_status()
Serializer-->>ViewSet: validated_data
ViewSet->>ViewSet: perform_update(serializer)
ViewSet->>Serializer: serializer.save()
Serializer->>DB: UPDATE SecurityIncident(...)
DB-->>Serializer: updated instance
Serializer-->>ViewSet: instance
ViewSet-->>Client: 200 OK
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro Knowledge base: Disabled due to 📒 Files selected for processing (1)
🧰 Additional context used🧬 Code graph analysis (1)website/tests/test_security_incident_views.py (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
🔇 Additional comments (10)
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. Comment |
📊 Monthly LeaderboardHi @Nachiket-Roy! Here's how you rank for December 2025:
Leaderboard based on contributions in December 2025. Keep up the great work! 🚀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (4)
website/serializers.py (1)
326-330: LGTM with minor robustness suggestion.The lowercase normalization correctly aligns with the model's
TextChoicesvalues (e.g.,"low","high","open"). While DRF's choice field validation should reject invalid types before these validators run, you might consider adding a defensive check for robustness:def validate_severity(self, value): + if not isinstance(value, str): + return value return value.lower() def validate_status(self, value): + if not isinstance(value, str): + return value return value.lower()website/tests/test_security_incident_models.py (1)
87-98: Potential test flakiness in ordering test.The test assumes
incident2will have a latercreated_atthanself.incident. In most cases this works due to microsecond precision, but under rare conditions (fast execution, low-precision clocks), both could share the same timestamp, causing non-deterministic ordering.Consider adding a small delay or explicitly setting
created_atto guarantee order:from django.utils import timezone from datetime import timedelta # Alternative approach using update to control timestamps incident2 = SecurityIncident.objects.create(...) SecurityIncident.objects.filter(pk=self.incident.pk).update( created_at=timezone.now() - timedelta(seconds=1) )website/api/views.py (1)
1465-1466: Minimal perform_update implementation.The
perform_updatemethod simply saves the serializer. This is functionally correct, though it could be omitted since the parent class provides identical behavior. Consider removing it unless you plan to add update-specific logic (e.g., history tracking, audit logging) later.website/tests/test_security_incident_api.py (1)
107-148: CRUD operations well tested.Create, update, and delete tests properly verify:
- Successful creation with correct status code (201) and data persistence
- Updates via PUT (leveraging the partial=True override)
- Deletion returns 204 and removes the record
One note: The update test sends all fields via PUT, which works correctly with the
partial=Trueoverride in the viewset. Consider adding a separate test for partial updates to ensure the intended behavior is explicitly verified.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (7)
website/api/views.py(1 hunks)website/serializers.py(1 hunks)website/tests/test_security_csv_export.py(1 hunks)website/tests/test_security_incident_api.py(1 hunks)website/tests/test_security_incident_forms.py(1 hunks)website/tests/test_security_incident_models.py(1 hunks)website/tests/test_security_incident_views.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
website/tests/test_security_incident_forms.py (2)
website/models.py (3)
SecurityIncident(3577-3629)Severity(3578-3582)Status(3584-3587)website/security_incident_form.py (1)
SecurityIncidentForm(6-47)
website/tests/test_security_incident_views.py (1)
website/models.py (4)
SecurityIncident(3577-3629)SecurityIncidentHistory(3632-3647)Severity(3578-3582)Status(3584-3587)
website/tests/test_security_incident_api.py (2)
website/models.py (3)
SecurityIncident(3577-3629)Severity(3578-3582)Status(3584-3587)website/tests/test_security_incident_views.py (4)
setUp(13-34)setUp(92-98)setUp(136-147)setUp(198-216)
website/tests/test_security_incident_models.py (1)
website/models.py (4)
SecurityIncident(3577-3629)SecurityIncidentHistory(3632-3647)Severity(3578-3582)Status(3584-3587)
website/tests/test_security_csv_export.py (1)
website/views/security.py (1)
_escape_csv_formula(58-71)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Run Tests
- GitHub Check: docker-test
🔇 Additional comments (12)
website/tests/test_security_incident_forms.py (1)
7-50: Well-structured form tests.The test suite provides good coverage of
SecurityIncidentFormvalidation:
- Valid data acceptance
- Required field validation (
title)- Whitespace normalization in
affected_systems- Field presence verification
The test at lines 32-43 correctly validates the
clean_affected_systemsmethod's whitespace stripping behavior.website/tests/test_security_incident_models.py (2)
10-99: Comprehensive model test coverage.The
SecurityIncidentModelTestclass thoroughly validates:
- Basic creation and field values
__str__representation format- Automatic
resolved_attimestamp management on status transitions- All severity and status choices
- Default ordering by
-created_at
101-163: Good coverage for SecurityIncidentHistory model.Tests appropriately verify:
- History record creation with all fields
- Descending ordering by
changed_at- Cascade deletion behavior when parent incident is removed
website/api/views.py (2)
1458-1460: Forcing partial updates on all update requests.This override makes
PUTrequests behave likePATCH(partial updates). This is a deliberate design choice that allows clients to submit only changed fields. Document this behavior in API documentation to avoid confusion, as it deviates from REST conventions wherePUTtypically requires full resource replacement.
1462-1463: Reporter assignment on create.Correctly associates the authenticated user as the incident reporter during creation.
website/tests/test_security_incident_views.py (3)
149-192: Thorough history tracking tests.The update view tests comprehensively verify:
- History records are created when fields change
- Specific field changes are captured with old/new values
changed_byis correctly set to the updating user- Unchanged fields don't generate spurious history records
195-234: Good detail view test coverage.Tests verify the detail view renders incident data and includes history context (
history_entries,history_count).
10-86: Comprehensive dashboard access control tests.The test suite properly validates:
- Authentication requirement (redirect for anonymous)
- Staff permission requirement (403 for regular users)
- Access granted for staff and superusers
- Context data includes expected keys
website/tests/test_security_incident_api.py (3)
11-33: Well-organized test setup.The
setUpmethod creates a good variety of users (regular, staff, superuser) and a sample incident for testing. The use ofAPIClientis appropriate for DRF endpoint testing.
34-57: Thorough authentication and authorization tests.Tests correctly validate:
- Unauthenticated requests receive 403 (per
IsAdminUserpermission)- Regular users (non-staff) are denied access
- Staff users can access and retrieve incident data
69-105: Good filter coverage.Filter tests correctly use lowercase values (
"high","resolved") matching the model'sTextChoicesvalues and the serializer's normalization behavior.website/tests/test_security_csv_export.py (1)
79-141: Solid test coverage for CSV export functionality.The test suite comprehensively covers:
- Access control (superuser vs. staff)
- Content structure and headers
- Formula injection sanitization
- Integration with SecurityIncident model
The setup and teardown are properly structured, and the test assertions are appropriate.
|
@coderabbitai why the tests failed and my test_security_incident_views.py is also failing locally how to fix it? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 1154 🏁 Script executed: Length of output: 10165 🏁 Script executed: Length of output: 48 🏁 Script executed: Length of output: 56 🏁 Script executed: Length of output: 2884 🏁 Script executed: Length of output: 76 🏁 Script executed: Length of output: 135 🏁 Script executed: Length of output: 220 🏁 Script executed: Length of output: 3360 🏁 Script executed: Length of output: 27 🏁 Script executed: Length of output: 1255 🏁 Script executed: Length of output: 72 🏁 Script executed: Length of output: 3958 🏁 Script executed: Length of output: 8229 🏁 Script executed: Length of output: 11 🏁 Script executed: Length of output: 3448 🏁 Script executed: Length of output: 962 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 1750 🏁 Script executed: Length of output: 204 🏁 Script executed: Length of output: 22 🏁 Script executed: Length of output: 1097 🏁 Script executed: Length of output: 36711 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 271 🏁 Script executed: Length of output: 409 🏁 Script executed: Length of output: 1521 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 728 🏁 Script executed: Length of output: 921 🏁 Script executed: Length of output: 6902 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 593 🏁 Script executed: Length of output: 960 🏁 Script executed: Length of output: 436 🏁 Script executed: Length of output: 395 🏁 Script executed: Length of output: 337 🏁 Script executed: Length of output: 2051 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 510 🏁 Script executed: Length of output: 151 🏁 Script executed: Length of output: 210 🏁 Script executed: Length of output: 32792 🏁 Script executed: Length of output: 194 🏁 Script executed: Length of output: 136 🏁 Script executed: Length of output: 222 🏁 Script executed: Length of output: 1278 🏁 Script executed: Length of output: 761 @Nachiket-Roy: I've identified two issues causing the test failures: Issue 1: Template Syntax Error (CI Failure) ❌Location: Problem: Invalid Django template syntax {{ history_entries|slice:"0:1"|first.changed_at|date:"M j" }}You cannot chain Fix: Use one of these approaches: {# Option 1: Use index access #}
{{ history_entries.0.changed_at|date:"M j" }}
{# Option 2: Use with tag #}
{% with first_entry=history_entries|first %}
{{ first_entry.changed_at|date:"M j" }}
{% endwith %}Issue 2: Filter Case Mismatch (Local Failure) ❌Location: Problem: Your tests filter with uppercase strings, but the model stores lowercase values:
Fix: Update the test filter values to lowercase: def test_dashboard_filter_by_severity(self):
"""Test filtering incidents by severity"""
self.client.login(username="staffuser", password="testpass123")
response = self.client.get(reverse("security_dashboard"), {"severity": "critical"}) # Changed from "CRITICAL"
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Critical Security Issue")
self.assertNotContains(response, "Minor Issue")
def test_dashboard_filter_by_status(self):
"""Test filtering incidents by status"""
self.client.login(username="staffuser", password="testpass123")
response = self.client.get(reverse("security_dashboard"), {"status": "resolved"}) # Changed from "RESOLVED"
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Minor Issue")
self.assertNotContains(response, "Critical Security Issue")Complete Fixes1. Fix <div class="text-2xl font-bold text-gray-800 dark:text-gray-200">
{{ history_entries.0.changed_at|date:"M j" }}
</div>2. Fix # Line 63
response = self.client.get(reverse("security_dashboard"), {"severity": "critical"})
# Line 71
response = self.client.get(reverse("security_dashboard"), {"status": "resolved"})These changes will fix both the CI failures and your local test failures. The key issue is that Django's |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
website/templates/security/incidents/history_list.html (1)
70-70: Template syntax fix is correct; optional consistency tweakUsing
{{ history_entries.0.changed_at|date:"M j" }}inside the{% if history_entries %}guard is valid Django syntax and resolves the previous filter/attribute chaining error without changing behavior (still reads the first history entry).If you want stylistic consistency with the "Last Editor" block above, you could optionally mirror that pattern:
{% with first_entry=history_entries|first %} {{ first_entry.changed_at|date:"M j" }} {% endwith %}Purely cosmetic; current code is fine to ship.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (2)
website/templates/security/incidents/history_list.html(1 hunks)website/tests/test_security_incident_views.py(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- website/tests/test_security_incident_views.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Run Tests
- GitHub Check: docker-test
|
@coderabbitai could you check whether all issues have been resolved and whether the test coverage in this PR fully covers the implementation for #5113? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 4392 🏁 Script executed: Length of output: 1719 🏁 Script executed: Length of output: 1602 @Nachiket-Roy: Great news! Both issues have been resolved ✅ Fixed Issues
Test Coverage Analysis vs Issue #5113✅ Excellent Coverage (Implemented & Tested)Your test suite comprehensively covers:
|
There was a problem hiding this 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)
website/tests/test_security_incident_views.py (1)
1-9: Reorder imports: move statement after all imports.The
User = get_user_model()assignment on line 7 should come after all import statements. Currently, there's an import on line 8 that follows this assignment.from django.contrib.auth import get_user_model from django.test import TestCase from django.urls import reverse +from django.template import Context, Template from website.models import Issue, SecurityIncident, SecurityIncidentHistory User = get_user_model() -from django.template import Context, Template
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (1)
website/tests/test_security_incident_views.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
website/tests/test_security_incident_views.py (1)
website/models.py (4)
SecurityIncident(3577-3629)SecurityIncidentHistory(3632-3647)Severity(3578-3582)Status(3584-3587)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run Tests
🔇 Additional comments (4)
website/tests/test_security_incident_views.py (4)
11-18: LGTM!Clean test for the custom template filter. The assertion correctly validates the underscore-to-space replacement behavior.
21-113: Comprehensive dashboard test coverage.Good coverage of authentication boundaries, authorization levels, filtering, and context validation. The filter tests now correctly use lowercase values (
"critical","resolved") matching the model'sTextChoicesdefinitions.The new
test_dashboard_includes_related_security_issuestest addresses the coverage gap mentioned in PR objectives for security issues withlabel=4.
115-157: LGTM!Solid test coverage for the create view including access control and the important verification that
reporteris auto-assigned to the requesting user (line 156).
175-199: Good history tracking verification.The test correctly validates that history records capture old/new values and the user who made the change.
Consider making line 192 more specific to assert the exact expected count based on the 5 changed fields (
title,severity,status,affected_systems,description), which would catch regressions if history tracking stops working for specific fields.
There was a problem hiding this 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
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (1)
website/tests/test_security_incident_views.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
website/tests/test_security_incident_views.py (1)
website/models.py (4)
SecurityIncident(3577-3629)SecurityIncidentHistory(3632-3647)Severity(3578-3582)Status(3584-3587)
🔇 Additional comments (1)
website/tests/test_security_incident_views.py (1)
1-260: Comprehensive test coverage with past issues resolved.The test file provides thorough coverage of security incident views:
- Authentication and authorization boundaries (staff-only access, 403 for regular users)
- Dashboard filtering by severity and status with correct lowercase values
- CRUD operations with proper permission checks
- History tracking for updates (both changed and unchanged fields)
- Detail view rendering with history context
- Related security issues context (new test addressing PR objectives gap)
The past review issues have been properly addressed:
- Filter parameters now use lowercase values ("critical", "resolved") matching model TextChoices (lines 89, 97)
- History fixture values now use lowercase ("medium", "high") matching production behavior (lines 239-240)
|
@coderabbitai could you check if any issues remain to be covered? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 5803 🏁 Script executed: Length of output: 3663 🏁 Script executed: Length of output: 2631 @Nachiket-Roy: Great question! Let me provide you with a comprehensive coverage analysis: ✅ All Previous Issues RESOLVED
📊 Test Coverage SummaryTotal: 56 tests across 5 files Excellent Coverage ✅
|
Jayant2908
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work with the tests! Could you take a look into these:
1)External CAPTCHA / Third-party Call :-Add feature flags, mock network calls, and store secrets securely.
2)Performance / Database Queries:-Optimize queries with indexes and implement streaming for large CSV exports.
3)Test Isolation & Flakiness:-Mock network calls and time-sensitive operations to ensure stable tests.
4)Logging & PII Redaction:-Add structured logging; redact sensitive information from logs.
|
Thanks for the suggestions however, I don’t think the current tests or changes in this PR relate to the points mentioned (external CAPTCHA/third-party calls, PII logging, or performance optimizations). This PR is focused on Security Dashboard Phase 1 test coverage and behavior validation, without introducing new integrations, logging, or network calls. @coderabbitai could you please double-check whether the points mentioned by @Jayant2908 apply to the changes in this PR? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 572 🏁 Script executed: Length of output: 20525 🏁 Script executed: Length of output: 88 🏁 Script executed: Length of output: 3168 🏁 Script executed: Length of output: 687 🏁 Script executed: Length of output: 4121 @Nachiket-Roy: You are absolutely correct ✅ I've analyzed @Jayant2908's review and the actual code changes in this PR. His suggestions appear to be general security/quality best practices rather than issues specific to your PR's changes. Evidence from Code Analysis1. External CAPTCHA / Third-party Calls ❌ NOT applicable
2. Logging & PII Redaction ❌ NOT applicable
3. Performance / Database Queries ✅ Already implemented
4. Test Isolation & Flakiness ✅ Already handled
What Your PR Actually Does
Conclusion@Jayant2908's review appears to be offering general recommendations for future enhancements, not identifying actual issues with your PR. Your PR is focused on test coverage for Security Dashboard Phase 1—it's testing existing functionality, not introducing new integrations, logging, or performance concerns. Your assessment is correct. This PR properly adds comprehensive test coverage for the implemented Security Dashboard features without introducing the concerns mentioned in the review. 🎯 |
Jayant2908
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad LGTM thenn!!
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 1025 🏁 Script executed: Length of output: 159 🏁 Script executed: Length of output: 1272 🏁 Script executed: Length of output: 37051 🏁 Script executed: Length of output: 3476 🏁 Script executed: Length of output: 4095 @Nachiket-Roy: I found the issue! The test The Problem ❌Location: Error: Root Cause: The The Fix ✅Remove the def test_dashboard_includes_related_security_issues(self):
"""Test that dashboard includes related security issues with label=4"""
self.client.login(username="staffuser", password="testpass123")
Issue.objects.create(
description="Test issue",
url="https://example.com/issue",
label=4, # Remove captcha parameter
)
response = self.client.get(reverse("security_dashboard"))
self.assertEqual(response.status_code, 200)
self.assertIn("security_issues", response.context)
self.assertEqual(len(response.context["security_issues"]), 1)Why This WorksLooking at other tests in your codebase:
The Simply remove line 54 ( |
This PR introduces comprehensive test coverage for the Security Dashboard phase 1 feature, covering all critical components including models, views, forms, API endpoints, and CSV export functionality with security-focused testing.
Fixes: #5113
Changes Made
1. Test Files Added
test_security_incident_models.py: Unit tests forSecurityIncidentandSecurityIncidentHistorymodelsresolved_attimestamp handlingtest_security_incident_views.py: Tests for dashboard and incident management viewstest_security_incident_forms.py: Form validation teststest_security_incident_api.py: REST API endpoint teststest_security_csv_export.py: Security-focused CSV export tests2. Code Enhancements
get_querysetmethod toSecurityIncidentViewSetKey Features Tested
Security & Access Control
Data Integrity
resolved_at)Functionality
OWASP Compliance
=,+,-,@escaped)Summary by CodeRabbit
New Features
Improvements
Tests
✏️ Tip: You can customize this high-level summary in your review settings.