import csv import tempfile from pathlib import Path import pytest import torch # --- Fast tests (no model loading required) --- def test_app_imports_without_model_load(): """Importing app should not trigger model download.""" import app assert hasattr(app, "translate") assert hasattr(app, "_load_tokenizer") assert hasattr(app, "_load_model") def test_app_has_main_function(): """main() should be callable (UI construction).""" import app assert callable(app.main) def test_translate_accepts_generation_params(): """translate() signature must accept generation parameters.""" import inspect import app sig = inspect.signature(app.translate) params = list(sig.parameters.keys()) assert "max_new_tokens" in params assert "num_beams" in params assert "temperature" in params # --- Batch file parsing helper tests --- def _make_txt_file(lines: list[str]) -> str: """Create a temp .txt file and return its path.""" f = tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) f.write("\n".join(lines)) f.close() return f.name def _make_csv_file(rows: list[dict]) -> str: """Create a temp .csv file and return its path.""" f = tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False, newline="") writer = csv.DictWriter(f, fieldnames=rows[0].keys()) writer.writeheader() writer.writerows(rows) f.close() return f.name def test_parse_txt_file(): import app path = _make_txt_file(["Hello", "World", "Test"]) texts, original_data = app._parse_input_file(path) assert texts == ["Hello", "World", "Test"] assert original_data is None Path(path).unlink() def test_parse_csv_file(): import app path = _make_csv_file([{"id": "1", "text": "Hello"}, {"id": "2", "text": "World"}]) texts, original_data = app._parse_input_file(path) assert texts == ["Hello", "World"] assert original_data is not None assert len(original_data) == 2 assert original_data[0]["id"] == "1" Path(path).unlink() def test_parse_csv_missing_text_column(): import app path = _make_csv_file([{"id": "1", "content": "Hello"}]) try: app._parse_input_file(path) assert False, "Should have raised ValueError" except ValueError as e: assert "text" in str(e).lower() finally: Path(path).unlink() def test_write_txt_output(): import app output_path = app._write_output_file(["Bonjour", "Monde"], None, ".txt") content = Path(output_path).read_text() assert content == "Bonjour\nMonde" Path(output_path).unlink() def test_write_csv_output(): import app original_data = [{"id": "1", "text": "Hello"}, {"id": "2", "text": "World"}] output_path = app._write_output_file(["Bonjour", "Monde"], original_data, ".csv") with open(output_path, newline="") as f: reader = list(csv.DictReader(f)) assert len(reader) == 2 assert reader[0]["translation"] == "Bonjour" assert reader[0]["id"] == "1" assert reader[1]["translation"] == "Monde" Path(output_path).unlink() def test_translate_batch_exists(): import app assert callable(app.translate_batch) def test_demo_has_sidebar_and_tabs(): """Verify the demo contains the expected layout components.""" import app demo = app._build_demo() # Flatten all blocks in the demo blocks = demo.blocks # Check for key component types by looking at block values component_types = {type(b).__name__ for b in blocks.values()} assert "Tab" in component_types, f"Expected Tab component, found: {component_types}" assert "Slider" in component_types, f"Expected Slider component, found: {component_types}" assert "Dropdown" in component_types, f"Expected Dropdown component, found: {component_types}" def test_demo_has_three_sliders(): """Three sliders: max_new_tokens, num_beams, temperature.""" import app demo = app._build_demo() sliders = [b for b in demo.blocks.values() if type(b).__name__ == "Slider"] assert len(sliders) == 3, f"Expected 3 sliders, found {len(sliders)}" def test_demo_has_two_tabs(): """Two tabs: Single and Batch.""" import app demo = app._build_demo() tabs = [b for b in demo.blocks.values() if type(b).__name__ == "Tab"] assert len(tabs) == 2, f"Expected 2 tabs, found {len(tabs)}" # --- Slow tests (require CUDA + model download) --- gpu_available = torch.cuda.is_available() @pytest.fixture(scope="module") def loaded_app(): import app # Force model/tokenizer loading app._load_tokenizer() app._load_model() return app @pytest.mark.slow @pytest.mark.skipif(not gpu_available, reason="Requires CUDA") def test_name_to_code_matches_language_names(loaded_app): name_to_code, language_names = loaded_app._build_language_mappings() assert set(name_to_code.keys()) == set(language_names) @pytest.mark.slow @pytest.mark.skipif(not gpu_available, reason="Requires CUDA") def test_language_names_sorted(loaded_app): _, language_names = loaded_app._build_language_mappings() assert language_names == sorted(language_names) @pytest.mark.slow @pytest.mark.skipif(not gpu_available, reason="Requires CUDA") def test_all_codes_are_bcp47_tokens(loaded_app): name_to_code, _ = loaded_app._build_language_mappings() for name, code in name_to_code.items(): assert code.startswith("<2") and code.endswith(">"), f"Invalid code {code} for {name}" @pytest.mark.slow @pytest.mark.skipif(not gpu_available, reason="Requires CUDA") def test_translate_unsupported_language(loaded_app): with pytest.raises(ValueError, match="Unsupported language"): loaded_app.translate("hello", "FakeLanguage") @pytest.mark.slow @pytest.mark.skipif(not gpu_available, reason="Requires CUDA") def test_translate_returns_string(loaded_app): result = loaded_app.translate("Hello", "French") assert isinstance(result, str) assert len(result) > 0