from __future__ import annotations from typing import TYPE_CHECKING from unittest import mock import pytest from django.contrib.auth.models import Permission from django.contrib.auth.models import User from rest_framework import status from rest_framework.test import APIClient from rest_framework.test import APITestCase if TYPE_CHECKING: from pytest_mock import MockerFixture class TestChatStreamingViewInputValidation(APITestCase): def setUp(self) -> None: super().setUp() self.user = User.objects.create_superuser(username="temp_admin") self.client.force_authenticate(user=self.user) def _mock_ai_enabled(self) -> mock.MagicMock: """Return a mock AIConfig instance with ai_enabled=True.""" m = mock.MagicMock() m.ai_enabled = True return m def test_oversized_question_is_rejected(self) -> None: with mock.patch( "documents.views.AIConfig", return_value=self._mock_ai_enabled(), ): resp = self.client.post( "/api/documents/chat/", {"q": "x" * 4001}, format="json", ) assert resp.status_code == status.HTTP_400_BAD_REQUEST def test_missing_question_is_rejected(self) -> None: with mock.patch( "documents.views.AIConfig", return_value=self._mock_ai_enabled(), ): resp = self.client.post( "/api/documents/chat/", {}, format="json", ) assert resp.status_code == status.HTTP_400_BAD_REQUEST @pytest.mark.django_db class TestChatStreamingViewUnrestrictedFlag: """The document id filter may only be skipped (``unrestricted=True``) for an active superuser, never for a regular user -- regardless of what permissions that user holds. """ @pytest.fixture def mocked_stream_chat(self, mocker: MockerFixture) -> mock.MagicMock: """AI enabled, with stream_chat_with_documents patched so the view never touches the real vector store; returns the patched callable so tests can inspect how it was called. """ mocker.patch("documents.views.AIConfig").return_value.ai_enabled = True return mocker.patch( "documents.views.stream_chat_with_documents", return_value=iter(()), ) @pytest.fixture def viewer_client(self, user_client: APIClient, regular_user: User) -> APIClient: """The conftest regular-user client, granted the global view_document permission -- the minimum ViewDocumentsPermissions needs to reach the view at all. Model-level only: says nothing about which documents (if any) this user can actually see. """ regular_user.user_permissions.add( *Permission.objects.filter(codename="view_document"), ) return user_client @pytest.mark.parametrize( ("client_fixture", "expected_unrestricted"), [ pytest.param("admin_client", True, id="superuser_is_unrestricted"), pytest.param("viewer_client", False, id="regular_user_is_restricted"), ], ) def test_unrestricted_only_for_superuser( self, request: pytest.FixtureRequest, mocked_stream_chat: mock.MagicMock, client_fixture: str, *, expected_unrestricted: bool, ) -> None: """ GIVEN: - A superuser, or a regular user holding the global view_document permission (but no object-level document access) WHEN: - They post a chat question with no document_id THEN: - stream_chat_with_documents is called with unrestricted=True only for the superuser; the regular user is always unrestricted=False, regardless of their permissions """ client: APIClient = request.getfixturevalue(client_fixture) client.post( "/api/documents/chat/", data={"q": "What's in these documents?"}, format="json", ) assert ( mocked_stream_chat.call_args.kwargs["unrestricted"] is expected_unrestricted )