bottleneckcrm/backend/common/tests/test_documents.py
Ashwin bd79a7a4cc Refactor code style and improve error handling in analytics API
- Cleaned up admin.py by removing unnecessary dictionary wrapping for fields.
- Adjusted migration files to maintain consistent formatting and removed redundant lines.
- Updated models.py and tests to remove unnecessary blank lines and improve readability.
- Enhanced error handling in the analytics API to properly forward upstream status and messages.
- Added comprehensive tests for the analytics proxy to ensure correct status and body forwarding.
- Updated contributing guidelines to clarify the use of ruff for code style enforcement.
2026-08-07 15:58:35 +05:30

321 lines
12 KiB
Python

"""
Tests for document management views: list, create, detail, update, delete.
Run with: pytest common/tests/test_documents.py -v
"""
import pytest
from django.core.files.uploadedfile import SimpleUploadedFile
from rest_framework import status
from common.models import Document, Teams
@pytest.mark.django_db
class TestDocumentListView:
"""Tests for GET/POST /api/documents/"""
url = "/api/documents/"
def test_list_documents(self, admin_client):
response = admin_client.get(self.url)
assert response.status_code == status.HTTP_200_OK
assert "documents_active" in response.data
def test_create_document(self, admin_client):
test_file = SimpleUploadedFile(
"test.txt", b"file content", content_type="text/plain"
)
response = admin_client.post(
self.url,
{"title": "Test Document", "document_file": test_file},
format="multipart",
)
assert response.status_code == status.HTTP_201_CREATED
assert response.data["error"] is False
def test_unauthenticated(self, unauthenticated_client):
"""The request is refused, and refused as a response, not an exception.
DRF catches ``PermissionDenied`` in ``handle_exception`` and renders it,
so a test client never sees it raised. Wrapping the call in
``pytest.raises`` therefore failed with DID NOT RAISE no matter how the
endpoint behaved, which meant this test could never have caught the
access opening up.
"""
response = unauthenticated_client.get(self.url)
assert response.status_code == status.HTTP_403_FORBIDDEN
def test_list_documents_context_keys(self, admin_client, org_a):
"""Document list should include all expected context keys."""
response = admin_client.get(self.url)
assert response.status_code == status.HTTP_200_OK
data = response.data
assert "documents_active" in data
assert "documents_inactive" in data
assert "users" in data
assert "status_choices" in data
assert "search" in data
def test_create_document_with_shared_to(self, admin_client, org_a, admin_profile):
"""Create document with shared_to profiles via multipart with JSON string."""
import json
test_file = SimpleUploadedFile(
"shared.txt", b"shared content", content_type="text/plain"
)
response = admin_client.post(
self.url,
{
"title": "Shared Document",
"document_file": test_file,
"shared_to": json.dumps([str(admin_profile.id)]),
},
format="multipart",
)
assert response.status_code == status.HTTP_201_CREATED
assert response.data["error"] is False
def test_create_document_with_teams(self, admin_client, org_a):
"""Create document with teams via multipart with JSON string."""
import json
team = Teams.objects.create(name="Doc Team", description="DT", org=org_a)
test_file = SimpleUploadedFile(
"team_doc.txt", b"team content", content_type="text/plain"
)
response = admin_client.post(
self.url,
{
"title": "Team Document",
"document_file": test_file,
"teams": json.dumps([str(team.id)]),
},
format="multipart",
)
assert response.status_code == status.HTTP_201_CREATED
def test_create_document_duplicate_title(self, admin_client, org_a, admin_user):
"""Creating a document with a duplicate title should fail."""
Document.objects.create(
title="Duplicate Title",
document_file="docs/dup.txt",
org=org_a,
created_by=admin_user,
)
test_file = SimpleUploadedFile(
"dup.txt", b"dup content", content_type="text/plain"
)
response = admin_client.post(
self.url,
{"title": "Duplicate Title", "document_file": test_file},
format="multipart",
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
def test_list_documents_filter_by_title(self, admin_client, org_a, admin_user):
"""Filter documents by title query parameter."""
Document.objects.create(
title="Alpha Doc",
document_file="docs/alpha.txt",
org=org_a,
created_by=admin_user,
)
Document.objects.create(
title="Beta Doc",
document_file="docs/beta.txt",
org=org_a,
created_by=admin_user,
)
response = admin_client.get(self.url + "?title=Alpha")
assert response.status_code == status.HTTP_200_OK
active = response.data["documents_active"]["documents_active"]
assert len(active) == 1
assert active[0]["title"] == "Alpha Doc"
def test_list_documents_filter_by_title_search(
self, admin_client, org_a, admin_user
):
"""Filter documents by title triggers search flag."""
Document.objects.create(
title="Searchable Doc",
document_file="docs/search.txt",
status="active",
org=org_a,
created_by=admin_user,
)
response = admin_client.get(self.url + "?title=Searchable")
assert response.status_code == status.HTTP_200_OK
active = response.data["documents_active"]["documents_active"]
assert len(active) == 1
@pytest.mark.django_db
class TestDocumentDetailView:
"""Tests for GET/PUT/PATCH/DELETE /api/documents/<pk>/"""
def _url(self, pk):
return f"/api/documents/{pk}/"
def _create_doc(self, org, user, title="Sample Doc"):
"""Helper to create a document directly in the database."""
return Document.objects.create(
title=title,
document_file="docs/sample.txt",
org=org,
created_by=user,
)
def test_get_document(self, admin_client, org_a, admin_user):
doc = self._create_doc(org_a, admin_user)
response = admin_client.get(self._url(doc.pk))
assert response.status_code == status.HTTP_200_OK
assert "doc_obj" in response.data
def test_update_document(self, admin_client, org_a, admin_user):
doc = self._create_doc(org_a, admin_user)
response = admin_client.put(
self._url(doc.pk),
{"title": "Updated Title"},
format="multipart",
)
assert response.status_code == status.HTTP_200_OK
assert response.data["error"] is False
def test_delete_document(self, admin_client, org_a, admin_user):
doc = self._create_doc(org_a, admin_user)
response = admin_client.delete(self._url(doc.pk))
assert response.status_code == status.HTTP_200_OK
assert response.data["error"] is False
assert not Document.objects.filter(pk=doc.pk).exists()
def test_org_isolation(self, org_b_client, org_a, admin_user, admin_profile):
"""org_b_client cannot access a document belonging to org_a."""
doc = self._create_doc(org_a, admin_user)
response = org_b_client.get(self._url(doc.pk))
assert response.status_code == status.HTTP_404_NOT_FOUND
def test_get_document_context(self, admin_client, org_a, admin_user):
"""Document detail should return file type and users."""
doc = self._create_doc(org_a, admin_user)
response = admin_client.get(self._url(doc.pk))
assert response.status_code == status.HTTP_200_OK
assert "doc_obj" in response.data
assert "file_type_code" in response.data
assert "users" in response.data
def test_get_document_non_admin_not_shared(
self, user_client, org_a, admin_user, user_profile
):
"""Non-admin user not shared to cannot view the document."""
doc = self._create_doc(org_a, admin_user)
response = user_client.get(self._url(doc.pk))
assert response.status_code == status.HTTP_403_FORBIDDEN
def test_get_document_non_admin_shared_to(
self, user_client, org_a, admin_user, user_profile
):
"""Non-admin user who is shared_to can view the document."""
doc = self._create_doc(org_a, admin_user)
doc.shared_to.add(user_profile)
response = user_client.get(self._url(doc.pk))
assert response.status_code == status.HTTP_200_OK
def test_update_document_with_shared_to(
self, admin_client, org_a, admin_user, admin_profile
):
"""Update document with shared_to."""
import json
doc = self._create_doc(org_a, admin_user)
response = admin_client.put(
self._url(doc.pk),
{
"title": "Updated Sharing",
"shared_to": json.dumps([str(admin_profile.id)]),
},
format="multipart",
)
assert response.status_code == status.HTTP_200_OK
doc.refresh_from_db()
assert doc.shared_to.count() == 1
def test_update_document_with_teams(self, admin_client, org_a, admin_user):
"""Update document with teams."""
import json
doc = self._create_doc(org_a, admin_user)
team = Teams.objects.create(name="Update Team", description="UT", org=org_a)
response = admin_client.put(
self._url(doc.pk),
{
"title": "Team Update",
"teams": json.dumps([str(team.id)]),
},
format="multipart",
)
assert response.status_code == status.HTTP_200_OK
doc.refresh_from_db()
assert doc.teams.count() == 1
def test_delete_document_non_admin_own(
self, user_client, org_a, regular_user, user_profile
):
"""A non-admin can delete a document they uploaded.
This previously asserted 403 and said so in its own docstring: the
view compared `request.profile` (a Profile) with `doc.created_by` (a
**User** FK), so the branch was always False and nobody could remove
their own upload. The comparison now uses `created_by_id` against
`profile.user_id`. See `common/tests/test_documents_access.py` for the
full matrix, including the cases that must still be refused.
"""
doc = self._create_doc(org_a, regular_user)
# Manually set created_by since crum overrides it in tests
Document.objects.filter(id=doc.id).update(created_by=regular_user)
response = user_client.delete(self._url(doc.pk))
assert response.status_code == status.HTTP_200_OK
assert not Document.objects.filter(id=doc.id).exists()
def test_delete_document_non_admin_other(
self, user_client, org_a, admin_user, user_profile
):
"""Non-admin cannot delete someone else's document."""
doc = self._create_doc(org_a, admin_user)
response = user_client.delete(self._url(doc.pk))
assert response.status_code == status.HTTP_403_FORBIDDEN
def test_update_document_non_admin_not_shared(
self, user_client, org_a, admin_user, user_profile
):
"""Non-admin not shared to cannot update the document."""
doc = self._create_doc(org_a, admin_user)
response = user_client.put(
self._url(doc.pk),
{"title": "Not Allowed"},
format="multipart",
)
assert response.status_code == status.HTTP_403_FORBIDDEN
def test_patch_document(self, admin_client, org_a, admin_user):
"""Test partial update via PATCH."""
doc = self._create_doc(org_a, admin_user)
response = admin_client.patch(
self._url(doc.pk),
{"title": "Patched Title"},
format="json",
)
assert response.status_code == status.HTTP_200_OK
assert response.data["error"] is False
def test_patch_document_non_admin_forbidden(
self, user_client, org_a, admin_user, user_profile
):
"""Non-admin not shared to cannot PATCH the document."""
doc = self._create_doc(org_a, admin_user)
response = user_client.patch(
self._url(doc.pk),
{"title": "Nope"},
format="json",
)
assert response.status_code == status.HTTP_403_FORBIDDEN