import pytest import os import tempfile from unittest.mock import patch, MagicMock, mock_open from fastapi import HTTPException from fastapi.testclient import TestClient class TestTranslationRoutes: """Test cases for translation file management routes""" def test_upload_translation_unauthorized(self, client): """Test uploading translation file without authentication""" # Create a mock file mock_file = MagicMock() mock_file.filename = "test.xlsx" mock_file.read.return_value = b"test content" response = client.post("/upload-translations", files={"file": ("test.xlsx", b"test content", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}) assert response.status_code == 401 data = response.json() assert data["detail"] == "Unauthorized" @patch('routes.auth.os.path.exists') @patch('routes.auth.os.makedirs') @patch('builtins.open', new_callable=mock_open) def test_upload_translation_success(self, mock_file, mock_makedirs, mock_exists, client, auth_headers, temp_translation_dir): """Test successful translation file upload""" # Mock that file doesn't exist initially mock_exists.return_value = False # Create a mock file content file_content = b"test excel content" response = client.post( "/upload-translations", files={"file": ("test_translation.xlsx", file_content, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}, headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["message"] == "Translation file uploaded successfully" assert data["filename"] == "test_translation.xlsx" # Verify directory creation was attempted mock_makedirs.assert_called_once() @patch('routes.auth.os.path.exists') def test_upload_translation_file_already_exists(self, mock_exists, client, auth_headers): """Test uploading translation file when one already exists""" # Mock that file already exists mock_exists.return_value = True file_content = b"test excel content" response = client.post( "/upload-translations", files={"file": ("test_translation.xlsx", file_content, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}, headers=auth_headers ) assert response.status_code == 400 data = response.json() assert data["detail"] == "A translation file already exists. Please delete it first." @patch('routes.auth.os.path.exists') @patch('routes.auth.os.makedirs') @patch('builtins.open', side_effect=Exception("File write error")) def test_upload_translation_write_error(self, mock_file, mock_makedirs, mock_exists, client, auth_headers): """Test translation upload with file write error""" mock_exists.return_value = False file_content = b"test excel content" response = client.post( "/upload-translations", files={"file": ("test_translation.xlsx", file_content, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}, headers=auth_headers ) assert response.status_code == 500 data = response.json() assert "Upload failed" in data["detail"] @patch('routes.auth.os.path.exists') @patch('routes.auth.os.makedirs') @patch('builtins.open', new_callable=mock_open) @patch('routes.auth.os.remove') def test_upload_translation_cleanup_on_error(self, mock_remove, mock_file, mock_makedirs, mock_exists, client, auth_headers): """Test cleanup when translation upload fails""" # Mock that files don't exist initially mock_exists.return_value = False # Mock file write to succeed but metadata write to fail mock_file.side_effect = [ MagicMock(), # Translation file write succeeds Exception("Metadata write error") # Metadata write fails ] file_content = b"test excel content" response = client.post( "/upload-translations", files={"file": ("test_translation.xlsx", file_content, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}, headers=auth_headers ) assert response.status_code == 500 # The cleanup should happen in the exception handler, but since we're mocking os.path.exists # to return False, the cleanup won't be called. This test verifies the error handling works. def test_delete_translation_unauthorized(self, client): """Test deleting translation file without authentication""" response = client.delete("/delete-translation") assert response.status_code == 401 data = response.json() assert data["detail"] == "Unauthorized" @patch('routes.auth.os.path.exists') @patch('routes.auth.os.remove') @patch('routes.auth.os.listdir') @patch('routes.auth.os.rmdir') def test_delete_translation_success(self, mock_rmdir, mock_listdir, mock_remove, mock_exists, client, auth_headers): """Test successful translation file deletion""" # Mock that files exist mock_exists.side_effect = lambda path: "translation.xlsx" in path or "metadata.txt" in path # Mock empty directory after deletion mock_listdir.return_value = [] response = client.delete("/delete-translation", headers=auth_headers) assert response.status_code == 200 data = response.json() assert data["message"] == "Translation file deleted successfully" # Verify files were deleted assert mock_remove.call_count == 2 # Translation file and metadata @patch('routes.auth.os.path.exists') def test_delete_translation_not_found(self, mock_exists, client, auth_headers): """Test deleting translation file when none exists""" # Mock that no files exist mock_exists.return_value = False response = client.delete("/delete-translation", headers=auth_headers) assert response.status_code == 404 data = response.json() assert data["detail"] == "No translation file found" @patch('routes.auth.os.path.exists') @patch('routes.auth.os.remove') @patch('routes.auth.os.listdir') def test_delete_translation_directory_not_empty(self, mock_listdir, mock_remove, mock_exists, client, auth_headers): """Test deletion when directory is not empty after file removal""" # Mock that files exist mock_exists.side_effect = lambda path: "translation.xlsx" in path or "metadata.txt" in path # Mock non-empty directory after deletion mock_listdir.return_value = ["other_file.txt"] response = client.delete("/delete-translation", headers=auth_headers) assert response.status_code == 200 data = response.json() assert data["message"] == "Translation file deleted successfully" # Directory should not be removed since it's not empty assert mock_remove.call_count == 2 # Only files, not directory def test_download_translation_unauthorized(self, client): """Test downloading translation file without authentication""" response = client.get("/download-translation") assert response.status_code == 401 data = response.json() assert data["detail"] == "Unauthorized" @patch('routes.auth.os.path.exists') @patch('builtins.open', new_callable=mock_open, read_data=b"test content") def test_download_translation_success(self, mock_file, mock_exists, client, auth_headers): """Test successful translation file download""" # Mock that file exists mock_exists.return_value = True response = client.get("/download-translation", headers=auth_headers) assert response.status_code == 200 # Check response headers assert response.headers["content-type"] == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" assert "attachment" in response.headers["content-disposition"] # The filename should be in the content disposition header content_disposition = response.headers["content-disposition"] assert "filename" in content_disposition @patch('routes.auth.os.path.exists') @patch('builtins.open', new_callable=mock_open, read_data=b"test content") def test_download_translation_with_metadata(self, mock_file, mock_exists, client, auth_headers): """Test translation download with metadata filename""" # Mock that files exist mock_exists.side_effect = lambda path: True response = client.get("/download-translation", headers=auth_headers) assert response.status_code == 200 # Check that we get a valid response with proper headers assert response.headers["content-type"] == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" assert "attachment" in response.headers["content-disposition"] assert "filename" in response.headers["content-disposition"] @patch('routes.auth.os.path.exists') def test_download_translation_not_found(self, mock_exists, client, auth_headers): """Test downloading translation file when none exists""" # Mock that file doesn't exist mock_exists.return_value = False response = client.get("/download-translation", headers=auth_headers) assert response.status_code == 404 data = response.json() assert data["detail"] == "No translation file found" @patch('routes.auth.os.path.exists') @patch('builtins.open', side_effect=Exception("File read error")) def test_download_translation_read_error(self, mock_file, mock_exists, client, auth_headers): """Test translation download with file read error""" mock_exists.return_value = True # Should raise an exception when file read fails with pytest.raises(Exception, match="File read error"): client.get("/download-translation", headers=auth_headers) def test_check_translation_status_no_file(self, client): """Test translation status check when no file exists""" with patch('routes.auth.os.path.exists') as mock_exists: mock_exists.return_value = False response = client.get("/translations/status") assert response.status_code == 200 data = response.json() assert data["file_exists"] is False assert data["file_name"] is None @patch('routes.auth.os.path.exists') @patch('builtins.open', new_callable=mock_open, read_data=b"custom_filename.xlsx") def test_check_translation_status_with_file(self, mock_file, mock_exists, client): """Test translation status check when file exists""" # Mock that files exist mock_exists.side_effect = lambda path: True response = client.get("/translations/status") assert response.status_code == 200 data = response.json() assert data["file_exists"] is True assert data["file_name"] == "custom_filename.xlsx" @patch('routes.auth.os.path.exists') @patch('builtins.open', side_effect=Exception("Metadata read error")) def test_check_translation_status_metadata_error(self, mock_file, mock_exists, client): """Test translation status check with metadata read error""" # Mock that files exist mock_exists.side_effect = lambda path: True response = client.get("/translations/status") assert response.status_code == 200 data = response.json() # Should fall back to default filename assert data["file_exists"] is True assert data["file_name"] == "translation.xlsx" def test_get_latest_translation_no_file(self, client): """Test latest translation endpoint when no file exists""" with patch('routes.auth.os.path.exists') as mock_exists: mock_exists.return_value = False response = client.get("/translations/latest") assert response.status_code == 404 data = response.json() assert data["detail"] == "No translation file found" @patch('routes.auth.os.path.exists') @patch('builtins.open', new_callable=mock_open, read_data=b"test content") def test_get_latest_translation_success(self, mock_file, mock_exists, client): """Test successful latest translation download""" # Mock that files exist mock_exists.side_effect = lambda path: True response = client.get("/translations/latest") assert response.status_code == 200 # Check response headers assert response.headers["content-type"] == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" assert "attachment" in response.headers["content-disposition"] @patch('routes.auth.os.path.exists') @patch('builtins.open', new_callable=mock_open, read_data=b"test content") def test_get_latest_translation_with_metadata(self, mock_file, mock_exists, client): """Test latest translation download with metadata filename""" # Mock that files exist mock_exists.side_effect = lambda path: True response = client.get("/translations/latest") assert response.status_code == 200 # Check that we get a valid response with proper headers assert response.headers["content-type"] == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" assert "attachment" in response.headers["content-disposition"] assert "filename" in response.headers["content-disposition"] def test_upload_translation_invalid_file_type(self, client, auth_headers): """Test uploading non-Excel file""" file_content = b"not an excel file" response = client.post( "/upload-translations", files={"file": ("test.txt", file_content, "text/plain")}, headers=auth_headers ) # Should still accept the file since validation is not strict assert response.status_code in [200, 400] # Depends on implementation def test_upload_translation_empty_file(self, client, auth_headers): """Test uploading empty file""" with patch('routes.auth.os.path.exists') as mock_exists: mock_exists.return_value = False response = client.post( "/upload-translations", files={"file": ("empty.xlsx", b"", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}, headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["message"] == "Translation file uploaded successfully" def test_upload_translation_large_file(self, client, auth_headers): """Test uploading large file""" with patch('routes.auth.os.path.exists') as mock_exists: mock_exists.return_value = False # Create a large file content (1MB) large_content = b"x" * (1024 * 1024) response = client.post( "/upload-translations", files={"file": ("large.xlsx", large_content, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}, headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["message"] == "Translation file uploaded successfully" @patch('routes.auth.os.path.exists') @patch('routes.auth.os.makedirs') @patch('builtins.open', new_callable=mock_open) def test_upload_translation_no_filename(self, mock_file, mock_makedirs, mock_exists, client, auth_headers): """Test uploading file with minimal filename""" mock_exists.return_value = False file_content = b"test content" response = client.post( "/upload-translations", files={"file": ("test.xlsx", file_content, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}, headers=auth_headers ) # Should handle the upload successfully assert response.status_code == 200 data = response.json() assert data["filename"] == "test.xlsx"