""" Comprehensive unit tests for the AutoApp Builder Hugging Face Space. All tests run offline -- no external API calls are made. """ import ast import sys from pathlib import Path from unittest.mock import patch, MagicMock import pytest # --------------------------------------------------------------------------- # Path setup -- ensure ``app`` package is importable from the hf/ directory. # --------------------------------------------------------------------------- HF_ROOT = Path(__file__).resolve().parent.parent if str(HF_ROOT) not in sys.path: sys.path.insert(0, str(HF_ROOT)) from app.engine.app_planner import AppPlanner, APP_TEMPLATES from app.engine.model_recommender import ModelRecommender, MODEL_CATALOG from app.codegen.repo_generator import RepoGenerator from app.codegen.gradio_generator import GradioGenerator, GRADIO_TEMPLATES from app.codegen.docker_generator import DockerGenerator, DOCKER_TEMPLATES from app.codegen.readme_generator import ReadmeGenerator from app.validators.code_checker import CodeChecker def _can_run_fastapi_tests(): """Check if FastAPI integration tests can run in this environment.""" try: from starlette.testclient import TestClient from app.main import app with TestClient(app, raise_server_exceptions=False) as c: resp = c.get("/") return resp.status_code != 500 except Exception: return False # =================================================================== # 1. AppPlanner # =================================================================== class TestAppPlanner: """Tests for AppPlanner.analyze().""" def setup_method(self): self.planner = AppPlanner() # -- SDK auto-selection ------------------------------------------------ def test_sdk_auto_selects_gradio_for_chatbot(self): plan = self.planner.analyze("Build a chatbot that answers questions", "auto") assert plan["sdk"] == "gradio" def test_sdk_auto_selects_gradio_for_image_classifier(self): plan = self.planner.analyze( "Build a Gradio app that classifies images using ResNet", "auto" ) assert plan["sdk"] == "gradio" def test_sdk_auto_selects_docker_for_api_service(self): plan = self.planner.analyze( "Build a REST API service with FastAPI endpoints", "auto" ) assert plan["sdk"] == "docker" def test_sdk_auto_selects_static_for_landing_page(self): plan = self.planner.analyze( "Create a beautiful static landing page for my portfolio", "auto" ) assert plan["sdk"] == "static" def test_sdk_preference_overrides_auto(self): plan = self.planner.analyze("Build a chatbot", "docker") assert plan["sdk"] == "docker" def test_sdk_defaults_to_gradio_when_no_signal(self): plan = self.planner.analyze("Do something cool", "auto") assert plan["sdk"] == "gradio" # -- App name generation (slugified) ----------------------------------- def test_app_name_is_slugified(self): plan = self.planner.analyze("Build an Image Classifier for dogs", "auto") name = plan["app_name"] assert " " not in name assert name == name.lower() # Should contain only lowercase alphanumeric and hyphens assert all(c.isalnum() or c == "-" for c in name) def test_app_name_contains_meaningful_words(self): plan = self.planner.analyze("Create a sentiment analysis dashboard", "auto") name = plan["app_name"] # Should contain at least one meaningful keyword assert any(kw in name for kw in ["sentiment", "analysis", "dashboard"]) def test_app_name_falls_back_when_only_stop_words(self): plan = self.planner.analyze("Build a the an is are", "auto") name = plan["app_name"] # Should still produce a valid slug (fallback to "my-app") assert len(name) > 0 assert "-" in name or name.isalnum() # -- Plan structure has required keys ---------------------------------- REQUIRED_KEYS = [ "sdk", "app_name", "app_type", "title", "description", "components", "model_task", "template_key", "original_prompt", ] @pytest.mark.parametrize("key", REQUIRED_KEYS) def test_plan_has_required_key(self, key): plan = self.planner.analyze("Build a chatbot", "auto") assert key in plan, f"Plan missing required key: {key}" def test_plan_components_is_list(self): plan = self.planner.analyze("Build a chatbot", "auto") assert isinstance(plan["components"], list) assert len(plan["components"]) > 0 # -- Template matching ------------------------------------------------- def test_chatbot_matches_chatbot_template(self): plan = self.planner.analyze("Build a chatbot", "auto") assert plan["template_key"] == "chatbot" def test_summarizer_matches_template(self): plan = self.planner.analyze("Build a text summarization tool", "auto") assert plan["template_key"] == "text_summarizer" def test_sentiment_matches_template(self): plan = self.planner.analyze("Create a sentiment analysis app", "auto") assert plan["template_key"] == "sentiment_analyzer" def test_portfolio_matches_template(self): plan = self.planner.analyze("Create a portfolio website to showcase my work", "auto") assert plan["template_key"] == "portfolio" def test_rest_api_matches_template(self): plan = self.planner.analyze("Build a REST API server with FastAPI", "auto") assert plan["template_key"] == "rest_api" # =================================================================== # 2. ModelRecommender # =================================================================== class TestModelRecommender: """Tests for ModelRecommender.""" def setup_method(self): self.recommender = ModelRecommender() def _make_plan(self, task: str) -> dict: return {"model_task": task} # -- Recommendations return valid model IDs ---------------------------- def test_recommend_returns_list(self): models = self.recommender.recommend(self._make_plan("text-generation")) assert isinstance(models, list) assert len(models) > 0 def test_recommended_models_have_id(self): models = self.recommender.recommend(self._make_plan("text-generation")) for m in models: assert "id" in m assert isinstance(m["id"], str) assert len(m["id"]) > 0 def test_recommended_models_have_required_fields(self): models = self.recommender.recommend(self._make_plan("summarization")) for m in models: assert "id" in m assert "desc" in m assert "size" in m assert "gpu_recommended" in m # -- Different task types produce different models --------------------- def test_different_tasks_produce_different_models(self): text_gen = self.recommender.recommend(self._make_plan("text-generation")) img_cls = self.recommender.recommend(self._make_plan("image-classification")) assert text_gen[0]["id"] != img_cls[0]["id"] def test_summarization_vs_translation(self): summ = self.recommender.recommend(self._make_plan("summarization")) trans = self.recommender.recommend(self._make_plan("translation")) assert summ[0]["id"] != trans[0]["id"] # -- Model size filtering (small/medium/large) ------------------------- def test_small_size_returns_models(self): models = self.recommender.recommend( self._make_plan("text-generation"), model_size="small" ) assert len(models) > 0 def test_large_size_returns_models(self): models = self.recommender.recommend( self._make_plan("text-generation"), model_size="large" ) assert len(models) > 0 def test_small_and_large_return_different_models(self): small = self.recommender.recommend( self._make_plan("text-generation"), model_size="small" ) large = self.recommender.recommend( self._make_plan("text-generation"), model_size="large" ) assert small[0]["id"] != large[0]["id"] def test_invalid_size_falls_back_to_medium(self): models = self.recommender.recommend( self._make_plan("text-generation"), model_size="xxl" ) medium = self.recommender.recommend( self._make_plan("text-generation"), model_size="medium" ) # Invalid size normalises to medium, so results should match assert models[0]["id"] == medium[0]["id"] def test_large_models_recommend_gpu(self): models = self.recommender.recommend( self._make_plan("text-generation"), model_size="large" ) assert models[0]["gpu_recommended"] is True def test_no_task_returns_empty(self): models = self.recommender.recommend({"model_task": None}) assert models == [] # -- get_primary_model ------------------------------------------------- def test_get_primary_model_returns_string(self): model_id = self.recommender.get_primary_model( self._make_plan("text-generation") ) assert isinstance(model_id, str) assert "/" in model_id or model_id.startswith("models?") def test_get_primary_model_none_for_missing_task(self): model_id = self.recommender.get_primary_model({"model_task": None}) assert model_id is None # -- All catalog tasks have all sizes ---------------------------------- @pytest.mark.parametrize("task", list(MODEL_CATALOG.keys())) def test_catalog_task_has_all_sizes(self, task): for size in ("small", "medium", "large"): assert size in MODEL_CATALOG[task], ( f"MODEL_CATALOG['{task}'] missing size '{size}'" ) # =================================================================== # 3. RepoGenerator # =================================================================== class TestRepoGenerator: """Tests for RepoGenerator (uses template fallbacks, no API).""" def setup_method(self): self.generator = RepoGenerator() def _make_plan(self, sdk, template_key=None, **overrides): plan = { "sdk": sdk, "app_name": "test-app", "app_type": template_key or "custom", "title": "Test App", "description": "A test application", "components": ["text_input", "text_output"], "model_task": "text-generation", "template_key": template_key, "original_prompt": "build a test app", "recommended_models": [ {"id": "Qwen/Qwen2.5-7B-Instruct", "desc": "Test", "size": "7B"} ], "extra_features": [], } plan.update(overrides) return plan # -- Gradio repo generation ------------------------------------------- def test_gradio_repo_has_app_py(self): plan = self._make_plan("gradio", "chatbot") files = self.generator.generate(plan, "build a chatbot") assert "app.py" in files def test_gradio_repo_has_requirements_txt(self): plan = self._make_plan("gradio", "chatbot") files = self.generator.generate(plan, "build a chatbot") assert "requirements.txt" in files assert "gradio" in files["requirements.txt"] def test_gradio_repo_has_readme(self): plan = self._make_plan("gradio", "chatbot") files = self.generator.generate(plan, "build a chatbot") assert "README.md" in files def test_gradio_repo_has_gitignore(self): plan = self._make_plan("gradio", "chatbot") files = self.generator.generate(plan, "build a chatbot") assert ".gitignore" in files # -- Docker repo generation ------------------------------------------- def test_docker_repo_has_dockerfile(self): plan = self._make_plan("docker", "rest_api") files = self.generator.generate(plan, "build a rest api") assert "Dockerfile" in files def test_docker_repo_has_app_py(self): plan = self._make_plan("docker", "rest_api") files = self.generator.generate(plan, "build a rest api") assert "app.py" in files def test_docker_repo_has_requirements(self): plan = self._make_plan("docker", "rest_api") files = self.generator.generate(plan, "build a rest api") assert "requirements.txt" in files def test_docker_repo_has_readme(self): plan = self._make_plan("docker", "rest_api") files = self.generator.generate(plan, "build a rest api") assert "README.md" in files # -- Static repo generation ------------------------------------------- def test_static_repo_has_index_html(self): plan = self._make_plan("static", "portfolio", model_task=None) files = self.generator.generate(plan, "build a portfolio site") assert "index.html" in files def test_static_repo_has_style_css(self): plan = self._make_plan("static", "portfolio", model_task=None) files = self.generator.generate(plan, "build a portfolio site") assert "style.css" in files def test_static_repo_has_readme(self): plan = self._make_plan("static", "portfolio", model_task=None) files = self.generator.generate(plan, "build a portfolio site") assert "README.md" in files def test_static_index_contains_title(self): plan = self._make_plan("static", "portfolio", title="My Portfolio", model_task=None) files = self.generator.generate(plan, "portfolio site") assert "My Portfolio" in files["index.html"] # -- Gradio requirements include task-specific deps -------------------- def test_gradio_image_task_includes_pillow(self): plan = self._make_plan("gradio", "image_classifier", model_task="image-classification") files = self.generator.generate(plan, "image classifier") assert "Pillow" in files["requirements.txt"] def test_gradio_chart_component_includes_matplotlib(self): plan = self._make_plan( "gradio", "sentiment_analyzer", model_task="text-classification", components=["text_input", "chart_output"], ) files = self.generator.generate(plan, "sentiment dashboard") assert "matplotlib" in files["requirements.txt"] # =================================================================== # 4. GradioGenerator # =================================================================== class TestGradioGenerator: """Tests for GradioGenerator (template fallback, no LLM calls).""" def setup_method(self): self.gen = GradioGenerator() def _make_plan(self, template_key, model_task="text-generation"): return { "template_key": template_key, "title": "Test App", "description": "A test", "model_task": model_task, "components": [], "recommended_models": [ {"id": "Qwen/Qwen2.5-7B-Instruct", "desc": "Test", "size": "7B"} ], "extra_features": [], } def test_generate_produces_valid_python_chatbot(self): plan = self._make_plan("chatbot") code = self.gen.generate(plan, "build a chatbot") # Must parse without syntax errors ast.parse(code) def test_generate_produces_valid_python_image_classifier(self): plan = self._make_plan("image_classifier", "image-classification") code = self.gen.generate(plan, "image classifier") ast.parse(code) def test_generate_produces_valid_python_summarizer(self): plan = self._make_plan("text_summarizer", "summarization") code = self.gen.generate(plan, "text summarizer") ast.parse(code) def test_generate_produces_valid_python_sentiment(self): plan = self._make_plan("sentiment_analyzer", "text-classification") code = self.gen.generate(plan, "sentiment tool") ast.parse(code) def test_generate_produces_valid_python_translator(self): plan = self._make_plan("translator", "translation") code = self.gen.generate(plan, "translator") ast.parse(code) def test_generate_produces_valid_python_qa(self): plan = self._make_plan("question_answering", "question-answering") code = self.gen.generate(plan, "question answering") ast.parse(code) def test_generate_produces_valid_python_text_gen(self): plan = self._make_plan("text_generator") code = self.gen.generate(plan, "text generator") ast.parse(code) # -- Fallback templates work without API ------------------------------- @pytest.mark.parametrize("key", list(GRADIO_TEMPLATES.keys())) def test_fallback_template_produces_valid_python(self, key): """Every built-in Gradio template must produce parseable Python.""" code = GRADIO_TEMPLATES[key].format( model_id="test/model", title="Test", description="Test description", ) ast.parse(code) def test_generic_fallback_for_unknown_template(self): plan = self._make_plan(None) code = self.gen.generate(plan, "something unknown") ast.parse(code) def test_generated_code_contains_gradio_import(self): plan = self._make_plan("chatbot") code = self.gen.generate(plan, "chatbot") assert "import gradio" in code def test_generated_code_contains_launch(self): plan = self._make_plan("chatbot") code = self.gen.generate(plan, "chatbot") assert "demo.launch()" in code # -- _extract_code helper ---------------------------------------------- def test_extract_code_from_markdown_block(self): raw = "Here is the code:\n```python\nimport gradio as gr\nprint('hi')\n```\nDone." code = self.gen._extract_code(raw) assert "import gradio" in code assert "```" not in code def test_extract_code_plain_python(self): raw = "import gradio as gr\nprint('hi')" code = self.gen._extract_code(raw) assert code == raw # =================================================================== # 5. DockerGenerator # =================================================================== class TestDockerGenerator: """Tests for DockerGenerator (template fallback, no LLM calls).""" def setup_method(self): self.gen = DockerGenerator() def _make_plan(self, template_key="rest_api"): return { "template_key": template_key, "title": "Test API", "description": "A test API service", "model_task": "text-generation", "components": ["fastapi_app", "model_endpoint"], "recommended_models": [ {"id": "Qwen/Qwen2.5-7B-Instruct", "desc": "Test", "size": "7B"} ], "extra_features": [], } def test_generate_produces_dockerfile(self): files = self.gen.generate(self._make_plan(), "build an api") assert "Dockerfile" in files def test_generate_produces_app_py(self): files = self.gen.generate(self._make_plan(), "build an api") assert "app.py" in files def test_generate_produces_requirements(self): files = self.gen.generate(self._make_plan(), "build an api") assert "requirements.txt" in files def test_app_py_is_valid_python(self): files = self.gen.generate(self._make_plan(), "build an api") ast.parse(files["app.py"]) def test_dockerfile_has_from(self): files = self.gen.generate(self._make_plan(), "build an api") assert "FROM" in files["Dockerfile"] def test_dockerfile_exposes_7860(self): files = self.gen.generate(self._make_plan(), "build an api") assert "7860" in files["Dockerfile"] def test_dockerfile_has_cmd(self): files = self.gen.generate(self._make_plan(), "build an api") assert "CMD" in files["Dockerfile"] def test_generic_docker_fallback(self): plan = self._make_plan("unknown_template_key") files = self.gen.generate(plan, "build something") assert "app.py" in files assert "Dockerfile" in files ast.parse(files["app.py"]) # -- All docker templates produce valid Python ------------------------- @pytest.mark.parametrize("template_name", list(DOCKER_TEMPLATES.keys())) def test_docker_template_app_py_is_valid_python(self, template_name): template = DOCKER_TEMPLATES[template_name] code = template["app.py"].format( model_id="test/model", title="Test", description="Test desc", ) ast.parse(code) # -- _parse_files helper ----------------------------------------------- def test_parse_files_marker_format(self): text = ( "=== FILENAME: app.py ===\nprint('hello')\n" "=== FILENAME: requirements.txt ===\nfastapi\n" ) files = self.gen._parse_files(text) assert "app.py" in files assert "requirements.txt" in files def test_parse_files_empty_returns_empty(self): files = self.gen._parse_files("no files here") assert files == {} # =================================================================== # 6. ReadmeGenerator # =================================================================== class TestReadmeGenerator: """Tests for ReadmeGenerator.""" def setup_method(self): self.gen = ReadmeGenerator() def _make_plan(self, app_type="chatbot", app_name="test-chatbot"): return { "app_type": app_type, "title": "Test Chatbot", "description": "A test chatbot application", "app_name": app_name, "recommended_models": [ {"id": "Qwen/Qwen2.5-7B-Instruct", "desc": "Test", "size": "7B"} ], "components": ["chat_interface", "system_prompt_config"], } # -- YAML frontmatter -------------------------------------------------- def test_readme_has_yaml_frontmatter(self): readme = self.gen.generate(self._make_plan(), "gradio") assert readme.startswith("---") # Should have opening and closing --- parts = readme.split("---") assert len(parts) >= 3 # before, frontmatter, after def test_frontmatter_contains_sdk_gradio(self): readme = self.gen.generate(self._make_plan(), "gradio") frontmatter = readme.split("---")[1] assert "sdk: gradio" in frontmatter def test_frontmatter_contains_sdk_docker(self): readme = self.gen.generate(self._make_plan("rest_api"), "docker") frontmatter = readme.split("---")[1] assert "sdk: docker" in frontmatter def test_frontmatter_contains_sdk_static(self): readme = self.gen.generate(self._make_plan("portfolio"), "static") frontmatter = readme.split("---")[1] assert "sdk: static" in frontmatter def test_frontmatter_contains_title(self): readme = self.gen.generate(self._make_plan(), "gradio") frontmatter = readme.split("---")[1] assert "Test Chatbot" in frontmatter # -- README body ------------------------------------------------------- def test_readme_contains_app_name_in_body(self): plan = self._make_plan(app_name="my-awesome-chatbot") plan["title"] = "My Awesome Chatbot" readme = self.gen.generate(plan, "gradio") # The title (which comes from the plan) should appear in the body assert "My Awesome Chatbot" in readme def test_readme_contains_description(self): readme = self.gen.generate(self._make_plan(), "gradio") assert "A test chatbot application" in readme def test_readme_contains_features_section(self): readme = self.gen.generate(self._make_plan(), "gradio") assert "## Features" in readme def test_readme_contains_model_reference(self): readme = self.gen.generate(self._make_plan(), "gradio") assert "Qwen/Qwen2.5-7B-Instruct" in readme def test_readme_contains_tech_stack(self): readme = self.gen.generate(self._make_plan(), "gradio") assert "## Tech Stack" in readme def test_docker_readme_mentions_fastapi(self): readme = self.gen.generate(self._make_plan("rest_api"), "docker") assert "FastAPI" in readme def test_static_readme_mentions_html(self): readme = self.gen.generate(self._make_plan("portfolio"), "static") assert "HTML" in readme # =================================================================== # 7. CodeChecker # =================================================================== class TestCodeChecker: """Tests for CodeChecker.""" def setup_method(self): self.checker = CodeChecker() # -- Python syntax validation ------------------------------------------ def test_valid_python_passes(self): files = {"app.py": "import os\nprint('hello')\n"} result = self.checker.check(files, "gradio") # Should have no python-specific errors (cross-file check may warn # about missing requirements.txt etc.) py_check = result["file_checks"]["app.py"] assert py_check["valid"] is True assert len(py_check["errors"]) == 0 def test_invalid_python_syntax_caught(self): files = {"app.py": "def foo(\n pass\n"} result = self.checker.check(files, "gradio") py_check = result["file_checks"]["app.py"] assert py_check["valid"] is False assert any("syntax error" in e.lower() for e in py_check["errors"]) def test_empty_file_flagged(self): files = {"app.py": ""} result = self.checker.check(files, "gradio") py_check = result["file_checks"]["app.py"] assert py_check["valid"] is False def test_dangerous_pattern_warned(self): files = {"app.py": "import os\nos.system('rm -rf /')\n"} result = self.checker.check(files, "gradio") py_check = result["file_checks"]["app.py"] assert any("os.system" in w for w in py_check["warnings"]) def test_eval_warned(self): files = {"app.py": "x = eval('1+2')\n"} result = self.checker.check(files, "gradio") py_check = result["file_checks"]["app.py"] assert any("eval" in w for w in py_check["warnings"]) # -- Dockerfile validation --------------------------------------------- def test_valid_dockerfile_passes(self): dockerfile = ( "FROM python:3.11-slim\n" "WORKDIR /app\n" "COPY . .\n" "EXPOSE 7860\n" "CMD [\"python\", \"app.py\"]\n" ) files = {"Dockerfile": dockerfile, "README.md": "---\ntest\n---\n"} result = self.checker.check(files, "docker") df_check = result["file_checks"]["Dockerfile"] assert len(df_check["errors"]) == 0 def test_dockerfile_missing_from_is_error(self): files = {"Dockerfile": "COPY . .\nCMD ['python']\n"} result = self.checker.check(files, "docker") df_check = result["file_checks"]["Dockerfile"] assert any("FROM" in e for e in df_check["errors"]) def test_dockerfile_missing_cmd_is_error(self): files = {"Dockerfile": "FROM python:3.11\nCOPY . .\n"} result = self.checker.check(files, "docker") df_check = result["file_checks"]["Dockerfile"] assert any("CMD" in e or "ENTRYPOINT" in e for e in df_check["errors"]) def test_dockerfile_missing_expose_is_warning(self): files = {"Dockerfile": "FROM python:3.11\nCMD ['python']\n"} result = self.checker.check(files, "docker") df_check = result["file_checks"]["Dockerfile"] assert any("EXPOSE" in w for w in df_check["warnings"]) # -- Cross-file checks ------------------------------------------------- def test_gradio_missing_app_py_is_error(self): files = {"README.md": "---\ntest\n---\n", "requirements.txt": "gradio\n"} result = self.checker.check(files, "gradio") assert result["valid"] is False assert any("app.py" in e for e in result["errors"]) def test_docker_missing_dockerfile_is_error(self): files = {"app.py": "print('hi')\n", "README.md": "---\ntest\n---\n"} result = self.checker.check(files, "docker") assert any("Dockerfile" in e for e in result["errors"]) def test_static_missing_index_html_is_error(self): files = {"README.md": "---\ntest\n---\n", "style.css": "body{}\n"} result = self.checker.check(files, "static") assert any("index.html" in e for e in result["errors"]) def test_overall_valid_when_no_errors(self): files = { "app.py": "import gradio as gr\nprint('hi')\n", "requirements.txt": "gradio>=5.0\n", "README.md": "---\nsdk: gradio\n---\n# App\n", } result = self.checker.check(files, "gradio") assert result["valid"] is True # -- HTML validation --------------------------------------------------- def test_valid_html_passes(self): html = "" files = {"index.html": html} result = self.checker.check(files, "static") html_check = result["file_checks"]["index.html"] assert len(html_check["errors"]) == 0 def test_html_missing_tags_warned(self): files = {"index.html": "
Hello
"} result = self.checker.check(files, "static") html_check = result["file_checks"]["index.html"] assert len(html_check["warnings"]) > 0 # -- README validation ------------------------------------------------- def test_readme_missing_frontmatter_warned(self): files = {"README.md": "# My App\nNo frontmatter here.\n"} result = self.checker.check(files, "gradio") readme_check = result["file_checks"]["README.md"] assert any("frontmatter" in w.lower() for w in readme_check["warnings"]) # =================================================================== # 8. FastAPI App Integration Tests # =================================================================== @pytest.mark.skipif( not _can_run_fastapi_tests(), reason="Jinja2 version incompatible with Starlette TestClient in this environment", ) class TestFastAPIApp: """Integration tests for the FastAPI app endpoints.""" def test_home_returns_200(self, client): resp = client.get("/") assert resp.status_code == 200 assert "text/html" in resp.headers["content-type"] def test_home_contains_title(self, client): resp = client.get("/") assert "AutoApp" in resp.text or "autoapp" in resp.text.lower() or " recommender -> generator -> checker pipeline offline (no LLM calls).""" def setup_method(self): self.planner = AppPlanner() self.recommender = ModelRecommender() self.generator = RepoGenerator() self.checker = CodeChecker() def _run_pipeline(self, prompt, sdk_pref="auto", model_size="medium"): plan = self.planner.analyze(prompt, sdk_pref) models = self.recommender.recommend(plan, model_size) plan["recommended_models"] = models plan["extra_features"] = [] files = self.generator.generate(plan, prompt) validation = self.checker.check(files, plan["sdk"]) return plan, files, validation def test_chatbot_e2e(self): plan, files, validation = self._run_pipeline("Build a chatbot") assert plan["sdk"] == "gradio" assert "app.py" in files assert validation["valid"] is True def test_image_classifier_e2e(self): plan, files, validation = self._run_pipeline( "Build a Gradio image classifier" ) assert plan["sdk"] == "gradio" assert "app.py" in files ast.parse(files["app.py"]) def test_rest_api_e2e(self): plan, files, validation = self._run_pipeline( "Build a REST API with FastAPI endpoints" ) assert plan["sdk"] == "docker" assert "Dockerfile" in files assert "app.py" in files ast.parse(files["app.py"]) def test_portfolio_e2e(self): plan, files, validation = self._run_pipeline( "Create a portfolio website to showcase projects" ) assert plan["sdk"] == "static" assert "index.html" in files def test_summarizer_small_model_e2e(self): plan, files, validation = self._run_pipeline( "Build a text summarization tool", model_size="small" ) assert plan["sdk"] == "gradio" assert "app.py" in files assert validation["valid"] is True