feat(audit): extend audit coverage to all admin interactions (v1.13.0)
- _audit_log helper: optional user= kwarg for login-time calls - AdminLoginView: auth.admin_login / auth.admin_login_failed - PartnerStatusView: partner.status_changed (atomic) - PartnerOnboardView: partner.onboarded - PartnerStaffCreateView: partner.staff.created - EventCreateView/UpdateView/DeleteView: event.created/updated/deleted (atomic) - EventPrimaryImageView: event.primary_image_changed - SettlementReleaseView: settlement.released (atomic) - ReviewDeleteView: review.deleted (atomic) - LeadUpdateView: lead.updated - PaymentGatewaySettingsView: gateway.created/updated/deleted - tests: AuthAuditEmissionTests + EventCrudAuditTests (16 total, all green) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -229,3 +229,97 @@ class UserStatusAuditEmissionTests(_AuditTestBase):
|
||||
).exists(),
|
||||
'reinstate did not emit audit log',
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AdminLoginView — audit emission
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class AuthAuditEmissionTests(_AuditTestBase):
|
||||
"""Successful and failed logins must leave matching rows in AuditLog."""
|
||||
|
||||
url = '/api/v1/admin/auth/login/'
|
||||
|
||||
def test_successful_login_emits_audit_row(self):
|
||||
resp = self.client.post(
|
||||
self.url,
|
||||
data={'username': self.admin.username, 'password': 'irrelevant'},
|
||||
content_type='application/json',
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200, resp.content)
|
||||
log = AuditLog.objects.filter(
|
||||
action='auth.admin_login', target_id=str(self.admin.id),
|
||||
).first()
|
||||
self.assertIsNotNone(log, 'successful login did not emit audit log')
|
||||
self.assertEqual(log.details.get('username'), self.admin.username)
|
||||
|
||||
def test_failed_login_emits_audit_row(self):
|
||||
resp = self.client.post(
|
||||
self.url,
|
||||
data={'username': self.admin.username, 'password': 'wrong-password'},
|
||||
content_type='application/json',
|
||||
)
|
||||
self.assertEqual(resp.status_code, 401, resp.content)
|
||||
self.assertTrue(
|
||||
AuditLog.objects.filter(action='auth.admin_login_failed').exists(),
|
||||
'failed login did not emit audit log',
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# EventCreateView / EventUpdateView / EventDeleteView — audit emission
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class EventCrudAuditTests(_AuditTestBase):
|
||||
"""Event CRUD operations must emit matching audit rows."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
from events.models import EventType
|
||||
self.event_type = EventType.objects.create(event_type='Test Category')
|
||||
|
||||
def _create_event_id(self):
|
||||
resp = self.client.post(
|
||||
'/api/v1/events/create/',
|
||||
data={'title': 'Test Event', 'eventType': self.event_type.id},
|
||||
content_type='application/json',
|
||||
**self.auth,
|
||||
)
|
||||
self.assertEqual(resp.status_code, 201, resp.content)
|
||||
return resp.json()['id']
|
||||
|
||||
def test_create_event_emits_audit_row(self):
|
||||
event_id = self._create_event_id()
|
||||
self.assertTrue(
|
||||
AuditLog.objects.filter(action='event.created', target_id=str(event_id)).exists(),
|
||||
'event create did not emit audit log',
|
||||
)
|
||||
|
||||
def test_update_event_emits_audit_row(self):
|
||||
event_id = self._create_event_id()
|
||||
AuditLog.objects.all().delete()
|
||||
resp = self.client.patch(
|
||||
f'/api/v1/events/{event_id}/update/',
|
||||
data={'title': 'Updated Title'},
|
||||
content_type='application/json',
|
||||
**self.auth,
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200, resp.content)
|
||||
log = AuditLog.objects.filter(action='event.updated', target_id=str(event_id)).first()
|
||||
self.assertIsNotNone(log, 'event update did not emit audit log')
|
||||
self.assertIn('title', log.details.get('changed_fields', []))
|
||||
|
||||
def test_delete_event_emits_audit_row(self):
|
||||
event_id = self._create_event_id()
|
||||
AuditLog.objects.all().delete()
|
||||
resp = self.client.delete(
|
||||
f'/api/v1/events/{event_id}/delete/',
|
||||
**self.auth,
|
||||
)
|
||||
self.assertEqual(resp.status_code, 204)
|
||||
self.assertTrue(
|
||||
AuditLog.objects.filter(action='event.deleted', target_id=str(event_id)).exists(),
|
||||
'event delete did not emit audit log',
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user