diff --git a/backend/fango/tests.py b/backend/fango/tests.py index 713783b..02d37eb 100644 --- a/backend/fango/tests.py +++ b/backend/fango/tests.py @@ -11,6 +11,7 @@ from rest_framework.exceptions import AuthenticationFailed import jwt import datetime +import tempfile SECRET_KEY = os.getenv('TOKEN_SECRET', 'secret') @@ -345,3 +346,216 @@ def test_rate_limit_under_limit(self, mock_ttl, mock_expire, mock_incr, mock_hge self.client.cookies["jwt"] = self.token response = self.client.get(self.protected_url) self.assertEqual(response.status_code, 200) + +from django.test import override_settings +from rest_framework.test import APIClient +from django.conf import settings +import jwt +import datetime +from django.core.files.uploadedfile import SimpleUploadedFile +import io +from PIL import Image +from fango.redis_client import redis_client +import json + +SECRET_KEY = os.getenv('TOKEN_SECRET', 'secret') + +class ImageTranslationTests(TestCase): + def setUp(self): + self.client = APIClient() + + self.lang = Language.objects.create(code="fr", lang="French") + + self.user = AppUser.objects.create_user( + email="test@test.com", + password="123", + name="Tester", + default_lang_id=self.lang + ) + + payload = { + "id": self.user.id, + "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1) + } + + self.token = jwt.encode(payload, SECRET_KEY, algorithm="HS256") + self.client.cookies["jwt"] = self.token + self.url = reverse("image-translate") + + def mock_redis_user(self, mock_hgetall): + mock_hgetall.return_value = { + "id": str(self.user.id), + "email": self.user.email, + "jwt": self.token + } + + @override_settings(MEDIA_ROOT=tempfile.gettempdir()) + @patch("image_handling.views.get_translation") + @patch("fango.middleware.JWTRedisMiddleware.redis_client.hgetall") + def test_image_translate_success(self, mock_hgetall, mock_translate): + + self.mock_redis_user(mock_hgetall) + + mock_translate.return_value = { + "english": "apple", + "meaning": "a fruit", + "translated": "pomme", + "translated-sentence-easy": "Je mange une pomme.", + "english-sentence-easy": "I eat an apple.", + "translated-sentence-med": "Il achète une pomme.", + "english-sentence-med": "He buys an apple.", + "translated-sentence-hard": "Elle prépare une tarte aux pommes.", + "english-sentence-hard": "She is making an apple pie." + } + + fake_img = Image.new("RGB", (100, 100), color="red") + buf = io.BytesIO() + fake_img.save(buf, format="JPEG") + buf.seek(0) + + uploaded = SimpleUploadedFile("test.jpg", buf.read(), content_type="image/jpeg") + + url = reverse("image-translate") + + response = self.client.post( + url, + { + "file":uploaded, + "target_lang": "french", + "target_lang_code":"fr" + }, + format="multipart" + ) + + self.assertEqual(response.status_code, 201) + self.assertIn("user_history_id", response.data) + + @override_settings(MEDIA_ROOT=tempfile.gettempdir()) + @patch("fango.middleware.JWTRedisMiddleware.redis_client.hgetall") + def test_missing_file(self, mock_hgetall): + self.mock_redis_user(mock_hgetall) + + response = self.client.post( + self.url, + { + "target_lang": "french", + "target_lang_code": "fr" + }, + format="multipart" + ) + self.assertEqual(response.status_code, 400) + self.assertIn("error", response.data) + self.assertEqual(response.data["error"], "No file uploaded") + + @patch("fango.middleware.JWTRedisMiddleware.redis_client.hgetall") + def test_file_not_image(self, mock_hgetall): + self.mock_redis_user(mock_hgetall) + + not_image = SimpleUploadedFile("test.txt", b"Not an image", content_type="text/plain") + + response = self.client.post( + self.url, + { + "file": not_image, + "target_lang": "french", + "target_lang_code": "fr" + }, + format="multipart" + ) + self.assertEqual(response.status_code, 400) + self.assertIn("error", response.data) + self.assertEqual(response.data["error"], "Uploaded file is not an image") + + @override_settings(MEDIA_ROOT=tempfile.gettempdir()) + @patch("image_handling.views.get_translation") + @patch("fango.middleware.JWTRedisMiddleware.redis_client.hgetall") + def test_openai_service_failure(self, mock_hgetall, mock_translate): + self.mock_redis_user(mock_hgetall) + mock_translate.side_effect = Exception("OpenAI API error") + fake_img = SimpleUploadedFile("test.jpg", b"fakeimage", content_type="image/jpeg") + response = self.client.post( + self.url, + {"file": fake_img, "target_lang": "french", "target_lang_code": "fr"}, + format="multipart" + ) + self.assertEqual(response.status_code, 500) + self.assertIn("Failed to get translation", response.data["error"]) + + @override_settings(MEDIA_ROOT=tempfile.gettempdir()) + @patch("image_handling.views.get_translation") + @patch("fango.middleware.JWTRedisMiddleware.redis_client.hgetall") + def test_translation_missing_keys(self, mock_hgetall, mock_translate): + self.mock_redis_user(mock_hgetall) + mock_translate.return_value = {"english": "apple"} + fake_img = Image.new("RGB", (100, 100), color="red") + buf = io.BytesIO() + fake_img.save(buf, format="JPEG") + buf.seek(0) + uploaded = SimpleUploadedFile("test.jpg", buf.read(), content_type="image/jpeg") + response = self.client.post( + self.url, + {"file": uploaded, "target_lang": "french", "target_lang_code": "fr"}, + format="multipart" + ) + self.assertEqual(response.status_code, 500) + self.assertIn("Missing data keys", response.data["error"]) + + @override_settings(MEDIA_ROOT=tempfile.gettempdir()) + @patch("image_handling.views.get_translation") + @patch("fango.middleware.JWTRedisMiddleware.redis_client.hgetall") + def test_db_save_failure(self, mock_hgetall, mock_translate): + self.mock_redis_user(mock_hgetall) + mock_translate.return_value = { + "english": "apple", + "meaning": "fruit", + "translated": "pomme", + "translated-sentence-easy": "Easy", + "english-sentence-easy": "Easy", + "translated-sentence-med": "Med", + "english-sentence-med": "Med", + "translated-sentence-hard": "Hard", + "english-sentence-hard": "Hard" + } + with patch("fango.models.Word.objects.create") as mock_word: + mock_word.side_effect = Exception("DB error") + fake_img = SimpleUploadedFile("test.jpg", b"fakeimage", content_type="image/jpeg") + response = self.client.post( + self.url, + {"file": fake_img, "target_lang": "french", "target_lang_code": "fr"}, + format="multipart" + ) + self.assertEqual(response.status_code, 500) + self.assertIn("Failed to save translation", response.data["error"]) + + + # ServeImage endpoint + @override_settings(MEDIA_ROOT=tempfile.gettempdir()) + @patch("fango.middleware.JWTRedisMiddleware.redis_client.hgetall") + def test_serve_image_success(self, mock_hgetall): + self.mock_redis_user(mock_hgetall) + save_folder = os.path.join(settings.MEDIA_ROOT, "images") + os.makedirs(save_folder, exist_ok=True) + file_path = os.path.join(save_folder, "test.jpg") + with open(file_path, "wb") as f: + f.write(b"fakeimagecontent") + + url = f"/api/media/images/test.jpg" + response = self.client.get(url) + + content_bytes = b"".join(response.streaming_content) + + self.assertEqual(response.status_code, 200) + self.assertEqual(content_bytes, b"fakeimagecontent") + + @override_settings(MEDIA_ROOT=tempfile.gettempdir()) + @patch("fango.middleware.JWTRedisMiddleware.redis_client.hgetall") + def test_serve_image_not_found(self, mock_hgetall): + self.mock_redis_user(mock_hgetall) + + url = f"/api/media/images/nonexistent.jpg" + response = self.client.get(url) + + self.assertEqual(response.status_code, 404) + + data = json.loads(response.content.decode()) + self.assertEqual(data["error"], "File not found") \ No newline at end of file diff --git a/backend/image_handling/views.py b/backend/image_handling/views.py index cea5138..64fa192 100644 --- a/backend/image_handling/views.py +++ b/backend/image_handling/views.py @@ -17,10 +17,7 @@ def post(self, request): uploaded_file = request.FILES.get("file") target_lang = request.POST.get("target_lang").lower() - # print("Language", target_lang) - target_lang_code = request.POST.get("target_lang_code").lower() - # print("Code", target_lang_code) if not target_lang: return Response({"error":"Missing target language"}, status=status.HTTP_400_BAD_REQUEST) @@ -34,11 +31,6 @@ def post(self, request): if not user_id: return Response({"error":"Invalid or missing authentication token"}, status=status.HTTP_401_UNAUTHORIZED) - # print("UserId", user_id) - # print("Received files:", uploaded_file.name) - # print("Content type:", uploaded_file.content_type) - # print("Size:", uploaded_file.size, "bytes") - # Get folder path save_folder = os.path.join(settings.MEDIA_ROOT, "images") diff --git a/frontend/.vite/deps/_metadata.json b/frontend/.vite/deps/_metadata.json new file mode 100644 index 0000000..9cb3b46 --- /dev/null +++ b/frontend/.vite/deps/_metadata.json @@ -0,0 +1,8 @@ +{ + "hash": "06d4cf61", + "configHash": "135df27c", + "lockfileHash": "2e84e3c9", + "browserHash": "9adfa4f0", + "optimized": {}, + "chunks": {} +} \ No newline at end of file diff --git a/frontend/.vite/deps/package.json b/frontend/.vite/deps/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/frontend/.vite/deps/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +}