From 1b8be73b13e7f6d5de575aba7c34d513fdfb70af Mon Sep 17 00:00:00 2001 From: Joe Lin Date: Sat, 22 Nov 2025 15:16:32 -0800 Subject: [PATCH 1/4] Readded env to docker-compose, checking if it messes with github actions --- docker-compose.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 6db1d2f..a372537 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,8 @@ services: - postgres_data:/var/lib/postgresql/data - img_data:/app/images # images stored separately - audio_data:/app/audio # audio stored separately + env_file: + - .env django-web: build: @@ -41,6 +43,8 @@ services: REDIS_HOST: ${REDIS_HOST} REDIS_PORT: ${REDIS_PORT} REDIS_DB: ${REDIS_DB} + env_file: + - .env frontend: build: From 24d8e08537e1c88dcf0f47d75c45c63350481f58 Mon Sep 17 00:00:00 2001 From: Joe Lin Date: Sun, 30 Nov 2025 13:09:26 -0800 Subject: [PATCH 2/4] Added testing for image translation and image serving --- backend/fango/tests.py | 214 ++++++++++++++++++++++++++++- frontend/.vite/deps/_metadata.json | 8 ++ frontend/.vite/deps/package.json | 3 + 3 files changed, 221 insertions(+), 4 deletions(-) create mode 100644 frontend/.vite/deps/_metadata.json create mode 100644 frontend/.vite/deps/package.json diff --git a/backend/fango/tests.py b/backend/fango/tests.py index e3c579c..fd10ec8 100644 --- a/backend/fango/tests.py +++ b/backend/fango/tests.py @@ -1,6 +1,212 @@ +import os +from .models import AppUser, UserHistory, Language, Word, Translation, UserHistory, Quiz, QuizWord +from .views import LoginView, LogoutView +from .redis_client import redis_client +from unittest.mock import patch +from django.urls import reverse +from django.utils import timezone from django.test import TestCase +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 -# Create your tests here. -class CITest(TestCase): - def test_ci_runs(self): - self.assertEqual(1, 1) \ No newline at end of file +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 + } + + @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) + + @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") + + @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"]) + + @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"]) + + @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 + @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") + + @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/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" +} From 9215b52460efc1d0ec68b3fe60c5e7420dbf9c88 Mon Sep 17 00:00:00 2001 From: Joe Lin Date: Sun, 30 Nov 2025 13:10:30 -0800 Subject: [PATCH 3/4] removed print statements --- backend/image_handling/views.py | 8 -------- 1 file changed, 8 deletions(-) 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") From a9115497466a41d024399523c4424518413f2e1f Mon Sep 17 00:00:00 2001 From: Joe Lin Date: Sun, 30 Nov 2025 13:24:23 -0800 Subject: [PATCH 4/4] fixing github actions --- backend/fango/tests.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/fango/tests.py b/backend/fango/tests.py index 77a23d6..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') @@ -388,6 +389,7 @@ def mock_redis_user(self, mock_hgetall): "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): @@ -428,6 +430,7 @@ def test_image_translate_success(self, mock_hgetall, mock_translate): 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) @@ -463,6 +466,7 @@ def test_file_not_image(self, mock_hgetall): 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): @@ -477,6 +481,7 @@ def test_openai_service_failure(self, mock_hgetall, mock_translate): 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): @@ -495,6 +500,7 @@ def test_translation_missing_keys(self, mock_hgetall, mock_translate): 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): @@ -521,7 +527,9 @@ def test_db_save_failure(self, mock_hgetall, mock_translate): 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) @@ -539,6 +547,7 @@ def test_serve_image_success(self, mock_hgetall): 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)