import pytest from django.test import Client from django.urls import reverse import io from unittest.mock import patch from django.core.files.uploadedfile import SimpleUploadedFile @pytest.fixture def client(): return Client() def test_upload_get(client): resp = client.get(reverse('identify_upload')) assert resp.status_code == 200 def test_confirm_redirects_without_session(client): resp = client.get(reverse('identify_confirm')) assert resp.status_code == 302 assert reverse('identify_upload') in resp['Location'] def test_fields_redirects_without_session(client): resp = client.get(reverse('identify_fields')) assert resp.status_code == 302 assert reverse('identify_upload') in resp['Location'] def _fake_image(name='test.jpg'): return SimpleUploadedFile(name, b'fake-image-bytes', content_type='image/jpeg') def test_upload_post_no_images(client): resp = client.post(reverse('identify_upload'), {}) assert resp.status_code == 200 assert b'select at least one' in resp.content.lower() or b'Please' in resp.content @pytest.mark.django_db def test_upload_post_calls_plantnet_and_redirects(client, settings): settings.PLANTNET_API_KEY = 'test-key' fake_matches = [ {'scientific_name': 'Liatris spicata', 'common_names': ['Blazing star'], 'score': 0.94, 'gbif_id': '123', 'family': 'Asteraceae', 'genus': 'Liatris'}, ] with patch('plants.views.identify.plantnet.identify', return_value=fake_matches): with patch('plants.views.identify.default_storage') as mock_storage: mock_storage.save.return_value = 'plants/temp_identify/abc_0.jpg' mock_storage.path.return_value = '/tmp/abc_0.jpg' resp = client.post(reverse('identify_upload'), { 'images': [_fake_image()], 'organs': ['flower'], }) assert resp.status_code == 302 assert reverse('identify_confirm') in resp['Location'] assert client.session['identify_matches'] == fake_matches def test_upload_post_plantnet_error_rerenders(client, settings): settings.PLANTNET_API_KEY = 'test-key' from plants.services.plantnet import PlantNetError with patch('plants.views.identify.plantnet.identify', side_effect=PlantNetError('No match found')): with patch('plants.views.identify.default_storage') as mock_storage: mock_storage.save.return_value = 'plants/temp_identify/abc_0.jpg' mock_storage.path.return_value = '/tmp/abc_0.jpg' mock_storage.exists.return_value = True resp = client.post(reverse('identify_upload'), { 'images': [_fake_image()], 'organs': ['flower'], }) assert resp.status_code == 200 assert b'No match found' in resp.content def _set_matches(client): session = client.session session['identify_matches'] = [ {'scientific_name': 'Liatris spicata', 'common_names': ['Blazing star'], 'score': 0.94, 'gbif_id': '123', 'family': 'Asteraceae', 'genus': 'Liatris'}, {'scientific_name': 'Liatris pycnostachya', 'common_names': ['Prairie blazing star'], 'score': 0.04, 'gbif_id': '456', 'family': 'Asteraceae', 'genus': 'Liatris'}, ] session['identify_image_paths'] = [{'path': 'plants/temp_identify/x_0.jpg', 'organ': 'flower'}] session.save() @pytest.mark.django_db def test_confirm_get_with_session(client): _set_matches(client) resp = client.get(reverse('identify_confirm')) assert resp.status_code == 200 assert b'Liatris spicata' in resp.content @pytest.mark.django_db def test_confirm_post_stores_selection_and_redirects(client): _set_matches(client) with patch('plants.views.identify.vpc_search', return_value=[ {'slug': 'liatris-spicata', 'common_name': 'Liatris spicata', 'scientific_name': 'Liatris spicata', 'thumbnail_url': '', 'source': 'vpc'}, ]): resp = client.post(reverse('identify_confirm'), {'match_idx': '0'}) assert resp.status_code == 302 assert reverse('identify_fields') in resp['Location'] assert client.session['identify_selected']['scientific_name'] == 'Liatris spicata' assert len(client.session['identify_vpc_results']) == 1 @pytest.mark.django_db def test_confirm_post_handles_vpc_failure(client): _set_matches(client) from plants.services.vpc import VPCError with patch('plants.views.identify.vpc_search', side_effect=VPCError('down')): resp = client.post(reverse('identify_confirm'), {'match_idx': '0'}) assert resp.status_code == 302 assert client.session['identify_vpc_results'] == [] from plants.models import Plant, Species def _set_fields_session(client, vpc_results=None): session = client.session session['identify_selected'] = { 'scientific_name': 'Liatris spicata', 'common_names': ['Blazing star', 'Pronkster'], 'score': 0.94, 'gbif_id': '123', 'family': 'Asteraceae', 'genus': 'Liatris', } session['identify_vpc_results'] = vpc_results if vpc_results is not None else [ {'slug': 'liatris-spicata', 'common_name': 'Liatris spicata', 'scientific_name': 'Liatris spicata', 'thumbnail_url': '', 'source': 'vpc'}, ] session['identify_vpc_idx'] = 0 session['identify_matches'] = [] session['identify_image_paths'] = [] session.save() @pytest.mark.django_db def test_fields_get_with_session(client): _set_fields_session(client) resp = client.get(reverse('identify_fields')) assert resp.status_code == 200 assert b'Liatris spicata' in resp.content @pytest.mark.django_db def test_fields_post_creates_species_and_plant(client): _set_fields_session(client) with patch('plants.views.identify.default_storage') as mock_storage: mock_storage.exists.return_value = False with patch('plants.views.identify._apply_vpc_care_fields'): resp = client.post(reverse('identify_fields'), { 'common_name_source': 'plantnet', 'scientific_name_source': 'plantnet', 'name': 'Liatris', }) assert resp.status_code == 302 assert Plant.objects.filter(name='Liatris').exists() species = Species.objects.get(scientific_name='Liatris spicata') assert species.common_name == 'Blazing star' @pytest.mark.django_db def test_fields_post_deduplicates_species(client): Species.objects.create(scientific_name='Liatris spicata', common_name='Old name') _set_fields_session(client) with patch('plants.views.identify.default_storage') as mock_storage: mock_storage.exists.return_value = False with patch('plants.views.identify._apply_vpc_care_fields'): client.post(reverse('identify_fields'), { 'common_name_source': 'plantnet', 'scientific_name_source': 'plantnet', 'name': 'Liatris', }) assert Species.objects.filter(scientific_name='Liatris spicata').count() == 1 @pytest.mark.django_db def test_fields_post_clears_session(client): _set_fields_session(client) with patch('plants.views.identify.default_storage') as mock_storage: mock_storage.exists.return_value = False with patch('plants.views.identify._apply_vpc_care_fields'): client.post(reverse('identify_fields'), { 'common_name_source': 'plantnet', 'scientific_name_source': 'plantnet', 'name': 'Liatris', }) assert 'identify_selected' not in client.session @pytest.mark.django_db def test_fields_post_uses_vpc_common_name(client): _set_fields_session(client, vpc_results=[ {'slug': 'liatris-spicata-kobold', 'common_name': "Liatris 'Kobold'", 'scientific_name': 'Liatris spicata', 'thumbnail_url': '', 'source': 'vpc'}, ]) with patch('plants.views.identify.default_storage') as mock_storage: mock_storage.exists.return_value = False with patch('plants.views.identify._apply_vpc_care_fields'): client.post(reverse('identify_fields'), { 'common_name_source': 'vpc', 'scientific_name_source': 'plantnet', 'name': 'Liatris Kobold', }) species = Species.objects.get(scientific_name='Liatris spicata') assert species.common_name == "Liatris 'Kobold'"