Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions forensics/ingest.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ def save_invalid_upload(
validation_error=error,
)
if fallback_group is not None:
audit_file.groups.add(fallback_group)
AuditGroup.objects.filter(id=fallback_group.id).update(updated_at=timezone.now())
return IngestionResult(audit_file=audit_file, created=True)
except IntegrityError:
Expand Down
52 changes: 52 additions & 0 deletions forensics/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,29 @@ def test_invalid_jsonl_returns_400_and_saves_quarantined_upload(self):
self.assertEqual(bad_event.raw_line, "{not-json}")
self.assertIn("JSON", bad_event.validation_error)

def test_invalid_utf8_group_upload_links_file_to_fallback_group(self):
raw_token, _token = UploadToken.issue("ios test client")
dump_bytes = b"\xff\xfeinvalid marmot audit bytes"

response = self.client.post(
reverse("api-group-audit-log-upload", kwargs={"group_slug": "mobile-qa"}),
data=dump_bytes,
content_type="application/x-ndjson",
HTTP_AUTHORIZATION=f"Bearer {raw_token}",
)

self.assertEqual(response.status_code, 400)
self.assertEqual(response.json()["validation_status"], AuditFile.STATUS_INVALID)
self.assertEqual(response.json()["group"], "mobile-qa")
self.assertEqual(response.json()["groups"], ["mobile-qa"])

audit_file = AuditFile.objects.get()
fallback_group = AuditGroup.objects.get(slug="mobile-qa")
self.assertEqual(audit_file.validation_status, AuditFile.STATUS_INVALID)
self.assertEqual(audit_file.events.get().group, fallback_group)
self.assertEqual(groups_for_audit_file(audit_file), [fallback_group])
self.assertEqual(list(audit_files_for_group(fallback_group)), [audit_file])

def test_invalid_utf8_upload_file_sha256_race_returns_existing_audit_file(self):
# save_invalid_upload() does the same check-then-create on file_sha256
# as the valid ingestion path. If a concurrent request inserts the same
Expand Down Expand Up @@ -2123,6 +2146,35 @@ def test_unexpected_ingest_error_quarantines_upload_not_500(self):
# The raw upload text is preserved intact as evidence, not dropped.
self.assertEqual(audit_file.raw_text, body)

def test_unexpected_ingest_error_group_upload_links_file_to_fallback_group(self):
from django.db import DataError

raw_token, _token = UploadToken.issue("ios test client")
body = representative_audit_log()

with mock.patch.object(
ingest_module, "create_events", side_effect=DataError("simulated bind overflow")
):
response = self.client.post(
reverse("api-group-audit-log-upload", kwargs={"group_slug": "mobile-qa"}),
data=body,
content_type="application/x-ndjson",
HTTP_AUTHORIZATION=f"Bearer {raw_token}",
)

self.assertEqual(response.status_code, 400)
self.assertEqual(response.json()["validation_status"], AuditFile.STATUS_INVALID)
self.assertEqual(response.json()["group"], "mobile-qa")
self.assertEqual(response.json()["groups"], ["mobile-qa"])

audit_file = AuditFile.objects.get()
fallback_group = AuditGroup.objects.get(slug="mobile-qa")
self.assertEqual(audit_file.validation_status, AuditFile.STATUS_INVALID)
self.assertEqual(audit_file.raw_text, body)
self.assertEqual(audit_file.events.get().group, fallback_group)
self.assertEqual(groups_for_audit_file(audit_file), [fallback_group])
self.assertEqual(list(audit_files_for_group(fallback_group)), [audit_file])

def test_all_supported_audit_kind_variants_are_normalized(self):
raw_token, _token = UploadToken.issue("ios test client")
cases = [
Expand Down