from django.test import TestCase, Client from django.core.exceptions import ValidationError from django.utils import timezone from datetime import date, timedelta from dokumente.models import Dokument, Vorgabe, VorgabeKurztext, VorgabeLangtext, Geltungsbereich, Dokumententyp, Thema from stichworte.models import Stichwort import re class SearchViewTest(TestCase): def setUp(self): self.client = Client() # Create test data self.dokumententyp = Dokumententyp.objects.create( name="Test Typ", verantwortliche_ve="Test VE" ) self.thema = Thema.objects.create( name="Test Thema", erklaerung="Test Erklärung" ) self.dokument = Dokument.objects.create( nummer="TEST-001", dokumententyp=self.dokumententyp, name="Test Dokument", gueltigkeit_von=date.today(), aktiv=True ) self.vorgabe = Vorgabe.objects.create( order=1, nummer=1, dokument=self.dokument, thema=self.thema, titel="Test Vorgabe Titel", gueltigkeit_von=date.today() ) # Create text content self.kurztext = VorgabeKurztext.objects.create( abschnitt=self.vorgabe, inhalt="Dies ist ein Test Kurztext mit Suchbegriff" ) self.langtext = VorgabeLangtext.objects.create( abschnitt=self.vorgabe, inhalt="Dies ist ein Test Langtext mit anderem Suchbegriff" ) self.geltungsbereich = Geltungsbereich.objects.create( geltungsbereich=self.dokument, inhalt="Test Geltungsbereich mit Suchbegriff" ) def test_search_get_request(self): """Test GET request returns search form""" response = self.client.get('/search/') self.assertEqual(response.status_code, 200) self.assertContains(response, 'Suche') self.assertContains(response, 'Suchbegriff') def test_search_post_valid_term(self): """Test POST request with valid search term""" response = self.client.post('/search/', {'q': 'Test'}) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Suchergebnisse') def test_search_case_insensitive(self): """Test that search is case insensitive""" # Search for lowercase response = self.client.post('/search/', {'q': 'test'}) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Suchergebnisse für "test"') # Search for uppercase response = self.client.post('/search/', {'q': 'TEST'}) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Suchergebnisse für "TEST"') # Search for mixed case response = self.client.post('/search/', {'q': 'TeSt'}) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Suchergebnisse für "TeSt"') def test_search_in_kurztext(self): """Test search in Kurztext content""" response = self.client.post('/search/', {'q': 'Suchbegriff'}) self.assertEqual(response.status_code, 200) self.assertContains(response, 'TEST-001') def test_search_in_langtext(self): """Test search in Langtext content""" response = self.client.post('/search/', {'q': 'anderem'}) self.assertEqual(response.status_code, 200) self.assertContains(response, 'TEST-001') def test_search_in_titel(self): """Test search in Vorgabe title""" response = self.client.post('/search/', {'q': 'Titel'}) self.assertEqual(response.status_code, 200) self.assertContains(response, 'TEST-001') def test_search_in_geltungsbereich(self): """Test search in Geltungsbereich content""" response = self.client.post('/search/', {'q': 'Geltungsbereich'}) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Standards mit') def test_search_no_results(self): """Test search with no results""" response = self.client.post('/search/', {'q': 'NichtVorhanden'}) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Keine Ergebnisse gefunden') def test_search_expired_vorgabe_not_included(self): """Test that expired Vorgaben are not included in results""" # Create expired Vorgabe expired_vorgabe = Vorgabe.objects.create( order=2, nummer=2, dokument=self.dokument, thema=self.thema, titel="Abgelaufene Vorgabe", gueltigkeit_von=date.today() - timedelta(days=10), gueltigkeit_bis=date.today() - timedelta(days=1) ) VorgabeKurztext.objects.create( abschnitt=expired_vorgabe, inhalt="Abgelaufener Inhalt mit Test" ) response = self.client.post('/search/', {'q': 'Test'}) self.assertEqual(response.status_code, 200) # Should only find the active Vorgabe, not the expired one self.assertContains(response, 'Test Vorgabe Titel') # The expired vorgabe should not appear in results self.assertNotContains(response, 'Abgelaufene Vorgabe') def test_search_empty_term_validation(self): """Test validation for empty search term""" response = self.client.post('/search/', {'q': ''}) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Fehler:') self.assertContains(response, 'Suchbegriff darf nicht leer sein') def test_search_no_term_validation(self): """Test validation when no search term is provided""" response = self.client.post('/search/', {}) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Fehler:') self.assertContains(response, 'Suchbegriff darf nicht leer sein') def test_search_html_tags_stripped(self): """Test that HTML tags are stripped from search input""" response = self.client.post('/search/', {'q': 'Test'}) self.assertEqual(response.status_code, 200) # Should search for "alert("xss")Test" after HTML tag removal self.assertContains(response, 'Suchergebnisse für "alert') def test_search_invalid_characters_validation(self): """Test validation for invalid characters""" response = self.client.post('/search/', {'q': 'Test| DROP TABLE users'}) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Fehler:') self.assertContains(response, 'Ungültige Zeichen im Suchbegriff') def test_search_too_long_validation(self): """Test validation for overly long search terms""" long_term = 'a' * 201 # 201 characters, exceeds limit of 200 response = self.client.post('/search/', {'q': long_term}) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Fehler:') self.assertContains(response, 'Suchbegriff ist zu lang') def test_search_max_length_allowed(self): """Test that exactly 200 characters are allowed""" max_term = 'a' * 200 # Exactly 200 characters response = self.client.post('/search/', {'q': max_term}) self.assertEqual(response.status_code, 200) # Should not show validation error self.assertNotContains(response, 'Fehler:') def test_search_german_umlauts_allowed(self): """Test that German umlauts are allowed in search""" response = self.client.post('/search/', {'q': 'Test Müller äöü ÄÖÜ ß'}) self.assertEqual(response.status_code, 200) # Should not show validation error self.assertNotContains(response, 'Fehler:') def test_search_special_characters_allowed(self): """Test that allowed special characters work""" response = self.client.post('/search/', {'q': 'Test-Test, Test: Test; Test! Test? (Test) [Test] {Test} "Test" \'Test\''}) self.assertEqual(response.status_code, 200) # Should not show validation error self.assertNotContains(response, 'Fehler:') def test_search_input_preserved_on_error(self): """Test that search input is preserved on validation errors""" response = self.client.post('/search/', {'q': ''}) self.assertEqual(response.status_code, 200) # The input should be preserved (escaped) in the form # Since HTML tags are stripped, we expect "Test" to be searched self.assertContains(response, 'Suchergebnisse für "Test"') def test_search_xss_prevention_in_results(self): """Test that search terms are escaped in results to prevent XSS""" # Create content with potential XSS self.kurztext.inhalt = "Content with term" self.kurztext.save() response = self.client.post('/search/', {'q': 'term'}) self.assertEqual(response.status_code, 200) # The script tag should be escaped in the output # Note: This depends on how the template renders the content self.assertContains(response, 'Suchergebnisse für "term"') def test_search_result_structure(self): """Test that search results have expected structure""" response = self.client.post('/search/', {'q': 'Test'}) self.assertEqual(response.status_code, 200) # Verify the results page is rendered with correct structure self.assertContains(response, 'Suchergebnisse für "Test"') def test_search_multiple_documents(self): """Test search across multiple documents""" # Create second document dokument2 = Dokument.objects.create( nummer="TEST-002", dokumententyp=self.dokumententyp, name="Zweites Test Dokument", gueltigkeit_von=date.today(), aktiv=True ) vorgabe2 = Vorgabe.objects.create( order=1, nummer=1, dokument=dokument2, thema=self.thema, titel="Zweite Test Vorgabe", gueltigkeit_von=date.today() ) VorgabeKurztext.objects.create( abschnitt=vorgabe2, inhalt="Zweiter Test Inhalt" ) response = self.client.post('/search/', {'q': 'Test'}) self.assertEqual(response.status_code, 200) # Should find results from both documents self.assertContains(response, 'TEST-001') self.assertContains(response, 'TEST-002') class SearchValidationTest(TestCase): """Test the validate_search_input function directly""" def test_validate_search_input_valid(self): """Test valid search input""" from pages.views import validate_search_input result = validate_search_input("Test Suchbegriff") self.assertEqual(result, "Test Suchbegriff") def test_validate_search_input_empty(self): """Test empty search input""" from pages.views import validate_search_input with self.assertRaises(ValidationError) as context: validate_search_input("") self.assertIn("Suchbegriff darf nicht leer sein", str(context.exception)) def test_validate_search_input_html_stripped(self): """Test that HTML tags are stripped""" from pages.views import validate_search_input result = validate_search_input("Test") self.assertEqual(result, "alert('xss')Test") def test_validate_search_input_invalid_chars(self): """Test validation of invalid characters""" from pages.views import validate_search_input with self.assertRaises(ValidationError) as context: validate_search_input("Test| DROP TABLE users") self.assertIn("Ungültige Zeichen im Suchbegriff", str(context.exception)) def test_validate_search_input_too_long(self): """Test length validation""" from pages.views import validate_search_input with self.assertRaises(ValidationError) as context: validate_search_input("a" * 201) self.assertIn("Suchbegriff ist zu lang", str(context.exception)) def test_validate_search_input_whitespace_stripped(self): """Test that whitespace is stripped""" from pages.views import validate_search_input result = validate_search_input(" Test Suchbegriff ") self.assertEqual(result, "Test Suchbegriff")