109 lines
3.8 KiB
Python
109 lines
3.8 KiB
Python
from datetime import timedelta
|
|
from unittest.mock import Mock, patch
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.core.cache import cache
|
|
from django.utils import timezone
|
|
from rest_framework.test import APIClient, APITestCase
|
|
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class WeatherEndpointTests(APITestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create_user(
|
|
username="weather-user",
|
|
email="weather@example.com",
|
|
password="password123",
|
|
)
|
|
self.client.force_authenticate(user=self.user)
|
|
cache.clear()
|
|
|
|
def test_daily_temperatures_rejects_too_many_days(self):
|
|
payload = {
|
|
"days": [
|
|
{"date": "2026-01-01", "latitude": 10, "longitude": 10}
|
|
for _ in range(61)
|
|
]
|
|
}
|
|
|
|
response = self.client.post(
|
|
"/api/weather/daily-temperatures/", payload, format="json"
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 400)
|
|
self.assertIn("maximum", response.json().get("error", "").lower())
|
|
|
|
@patch("adventures.views.weather_view.requests.get")
|
|
def test_daily_temperatures_future_date_returns_unavailable_without_external_call(
|
|
self, mock_requests_get
|
|
):
|
|
future_date = (timezone.now().date() + timedelta(days=10)).isoformat()
|
|
|
|
response = self.client.post(
|
|
"/api/weather/daily-temperatures/",
|
|
{"days": [{"date": future_date, "latitude": 12.34, "longitude": 56.78}]},
|
|
format="json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(
|
|
response.json()["results"][0],
|
|
{"date": future_date, "available": False, "temperature_c": None},
|
|
)
|
|
mock_requests_get.assert_not_called()
|
|
|
|
@patch("adventures.views.weather_view.requests.get")
|
|
def test_daily_temperatures_accepts_zero_lat_lon(self, mock_requests_get):
|
|
today = timezone.now().date().isoformat()
|
|
mocked_response = Mock()
|
|
mocked_response.raise_for_status.return_value = None
|
|
mocked_response.json.return_value = {
|
|
"daily": {
|
|
"temperature_2m_max": [20.0],
|
|
"temperature_2m_min": [10.0],
|
|
}
|
|
}
|
|
mock_requests_get.return_value = mocked_response
|
|
|
|
response = self.client.post(
|
|
"/api/weather/daily-temperatures/",
|
|
{"days": [{"date": today, "latitude": 0, "longitude": 0}]},
|
|
format="json",
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.json()["results"][0]["date"], today)
|
|
self.assertTrue(response.json()["results"][0]["available"])
|
|
self.assertEqual(response.json()["results"][0]["temperature_c"], 15.0)
|
|
|
|
|
|
class RecommendationPhotoProxyValidationTests(APITestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create_user(
|
|
username="reco-user",
|
|
email="reco@example.com",
|
|
password="password123",
|
|
)
|
|
self.client.force_authenticate(user=self.user)
|
|
|
|
def test_google_photo_rejects_invalid_photo_name(self):
|
|
response = self.client.get(
|
|
"/api/recommendations/google-photo/?photo_name=invalid-photo-name"
|
|
)
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
def test_google_photo_rejects_trailing_newline_photo_name(self):
|
|
response = self.client.get(
|
|
"/api/recommendations/google-photo/?photo_name=places/abc/photos/def%0A"
|
|
)
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
|
|
class MCPAuthTests(APITestCase):
|
|
def test_mcp_unauthenticated_access_is_rejected(self):
|
|
unauthenticated_client = APIClient()
|
|
response = unauthenticated_client.post("/api/mcp", {}, format="json")
|
|
self.assertIn(response.status_code, [401, 403])
|