bloombase/plants/tests/test_plantnet.py
Stephan Kerkman b884852f05 feat: add Pl@ntNet identification service
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 17:34:48 +02:00

107 lines
3.8 KiB
Python

import pytest
import requests as req_lib
from unittest.mock import patch, MagicMock
from plants.services.plantnet import identify, PlantNetError
def _mock_response(results=None, status_code=200):
mock = MagicMock()
mock.status_code = status_code
mock.json.return_value = {'results': results or []}
if status_code == 429:
mock.raise_for_status.side_effect = req_lib.HTTPError(response=mock)
elif status_code >= 400:
mock.raise_for_status.side_effect = req_lib.HTTPError(response=mock)
else:
mock.raise_for_status = MagicMock()
return mock
def _api_results():
return [
{
'score': 0.94,
'species': {
'scientificNameWithoutAuthor': 'Liatris spicata',
'commonNames': ['Blazing star', 'Pronkster'],
'family': {'scientificNameWithoutAuthor': 'Asteraceae'},
'genus': {'scientificNameWithoutAuthor': 'Liatris'},
},
'gbif': {'id': '2889739'},
},
{
'score': 0.04,
'species': {
'scientificNameWithoutAuthor': 'Liatris pycnostachya',
'commonNames': ['Prairie blazing star'],
'family': {'scientificNameWithoutAuthor': 'Asteraceae'},
'genus': {'scientificNameWithoutAuthor': 'Liatris'},
},
'gbif': {'id': '1234567'},
},
]
def test_identify_returns_empty_without_api_key(settings):
settings.PLANTNET_API_KEY = ''
with pytest.raises(PlantNetError, match='API key'):
identify([(b'fake', 'flower')])
def test_identify_parses_results(settings):
settings.PLANTNET_API_KEY = 'test-key'
with patch('plants.services.plantnet.requests.post',
return_value=_mock_response(_api_results())):
results = identify([(b'fake', 'flower')])
assert len(results) == 2
assert results[0]['scientific_name'] == 'Liatris spicata'
assert results[0]['score'] == pytest.approx(0.94)
assert 'Blazing star' in results[0]['common_names']
assert results[0]['family'] == 'Asteraceae'
assert results[0]['genus'] == 'Liatris'
assert results[0]['gbif_id'] == '2889739'
def test_identify_raises_on_no_results(settings):
settings.PLANTNET_API_KEY = 'test-key'
with patch('plants.services.plantnet.requests.post',
return_value=_mock_response([])):
with pytest.raises(PlantNetError, match='No match'):
identify([(b'fake', 'flower')])
def test_identify_raises_on_quota_exceeded(settings):
settings.PLANTNET_API_KEY = 'test-key'
with patch('plants.services.plantnet.requests.post',
return_value=_mock_response(status_code=429)):
with pytest.raises(PlantNetError, match='Daily limit'):
identify([(b'fake', 'flower')])
def test_identify_raises_on_timeout(settings):
settings.PLANTNET_API_KEY = 'test-key'
with patch('plants.services.plantnet.requests.post',
side_effect=req_lib.Timeout):
with pytest.raises(PlantNetError, match='timed out'):
identify([(b'fake', 'flower')])
def test_identify_raises_on_other_http_error(settings):
settings.PLANTNET_API_KEY = 'test-key'
with patch('plants.services.plantnet.requests.post',
return_value=_mock_response(status_code=500)):
with pytest.raises(PlantNetError):
identify([(b'fake', 'flower')])
def test_identify_sends_correct_organs(settings):
settings.PLANTNET_API_KEY = 'test-key'
mock_post = MagicMock(return_value=_mock_response(_api_results()))
with patch('plants.services.plantnet.requests.post', mock_post):
identify([(b'img1', 'flower'), (b'img2', 'leaf')])
call_kwargs = mock_post.call_args
data = call_kwargs[1]['data']
assert ('organs', 'flower') in data
assert ('organs', 'leaf') in data