Spaces:
Running
Running
| # Copyright (c) Meta Platforms, Inc. and affiliates. | |
| # All rights reserved. | |
| # | |
| # This source code is licensed under the BSD-style license found in the | |
| # LICENSE file in the root directory of this source tree. | |
| """Tests for the openenv push command.""" | |
| import os | |
| from pathlib import Path | |
| from unittest.mock import MagicMock, patch | |
| from openenv.cli.__main__ import app | |
| from typer.testing import CliRunner | |
| runner = CliRunner() | |
| def _create_test_openenv_env(env_dir: Path, env_name: str = "test_env") -> None: | |
| """Create a complete OpenEnv environment for testing.""" | |
| import yaml | |
| # Create openenv.yaml | |
| manifest = { | |
| "spec_version": 1, | |
| "name": env_name, | |
| "type": "space", | |
| "runtime": "fastapi", | |
| "app": "server.app:app", | |
| "port": 8000, | |
| } | |
| with open(env_dir / "openenv.yaml", "w") as f: | |
| yaml.dump(manifest, f) | |
| # Create pyproject.toml (required by validate_env_structure) | |
| pyproject_content = f"""[project] | |
| name = "{env_name}" | |
| version = "0.1.0" | |
| dependencies = ["openenv[core]>=0.2.0"] | |
| """ | |
| (env_dir / "pyproject.toml").write_text(pyproject_content) | |
| # Create __init__.py | |
| (env_dir / "__init__.py").write_text("# Test environment\n") | |
| # Create client.py (required by validate_env_structure) | |
| (env_dir / "client.py").write_text("# Test client\n") | |
| # Create models.py (required by validate_env_structure) | |
| (env_dir / "models.py").write_text("# Test models\n") | |
| # Create server directory and files | |
| (env_dir / "server").mkdir(exist_ok=True) | |
| (env_dir / "server" / "__init__.py").write_text("# Server module\n") | |
| (env_dir / "server" / "app.py").write_text("# App module\n") | |
| (env_dir / "server" / "Dockerfile").write_text( | |
| 'FROM openenv-base:latest\nCMD ["uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", "8000"]\n' | |
| ) | |
| # Create README.md with frontmatter | |
| readme_content = """--- | |
| title: Test Environment | |
| sdk: docker | |
| app_port: 8000 | |
| --- | |
| # Test Environment | |
| """ | |
| (env_dir / "README.md").write_text(readme_content) | |
| def test_push_validates_openenv_directory(tmp_path: Path) -> None: | |
| """Test that push validates openenv.yaml is present.""" | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| assert result.exit_code != 0 | |
| assert ( | |
| "openenv.yaml" in result.output.lower() or "manifest" in result.output.lower() | |
| ) | |
| def test_push_validates_openenv_yaml_format(tmp_path: Path) -> None: | |
| """Test that push validates openenv.yaml format.""" | |
| # Create complete env structure then overwrite openenv.yaml with invalid content | |
| _create_test_openenv_env(tmp_path) | |
| (tmp_path / "openenv.yaml").write_text("invalid: yaml: content: [") | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| assert result.exit_code != 0 | |
| assert "parse" in result.output.lower() or "yaml" in result.output.lower() | |
| def test_push_validates_openenv_yaml_has_name(tmp_path: Path) -> None: | |
| """Test that push validates openenv.yaml has a name field.""" | |
| import yaml | |
| # Create complete env structure then overwrite openenv.yaml without name | |
| _create_test_openenv_env(tmp_path) | |
| manifest = {"spec_version": 1, "type": "space"} | |
| with open(tmp_path / "openenv.yaml", "w") as f: | |
| yaml.dump(manifest, f) | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| assert result.exit_code != 0 | |
| assert "name" in result.output.lower() | |
| def test_push_authenticates_with_hf(tmp_path: Path) -> None: | |
| """Test that push ensures Hugging Face authentication.""" | |
| _create_test_openenv_env(tmp_path) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| # Mock whoami to return user info | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| # Mock HfApi | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| # Verify whoami was called | |
| assert mock_whoami.called | |
| def test_push_enables_web_interface_in_dockerfile(tmp_path: Path) -> None: | |
| """Test that push enables web interface in Dockerfile.""" | |
| _create_test_openenv_env(tmp_path) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| # Verify API was called (upload_folder) | |
| assert mock_api.upload_folder.called | |
| def test_push_updates_readme_frontmatter(tmp_path: Path) -> None: | |
| """Test that push updates README frontmatter with base_path.""" | |
| _create_test_openenv_env(tmp_path) | |
| # Create README without base_path | |
| readme_content = """--- | |
| title: Test Environment | |
| sdk: docker | |
| app_port: 8000 | |
| --- | |
| # Test Environment | |
| """ | |
| (tmp_path / "README.md").write_text(readme_content) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| # Verify API was called | |
| assert mock_api.upload_folder.called | |
| def test_push_uses_repo_id_option(tmp_path: Path) -> None: | |
| """Test that push respects --repo-id option.""" | |
| _create_test_openenv_env(tmp_path) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push", "--repo-id", "custom-org/my-env"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| # Verify create_repo was called with correct repo_id | |
| mock_api.create_repo.assert_called_once() | |
| call_args = mock_api.create_repo.call_args | |
| assert call_args.kwargs["repo_id"] == "custom-org/my-env" | |
| def test_push_uses_default_repo_id(tmp_path: Path) -> None: | |
| """Test that push uses default repo-id from username and env name.""" | |
| _create_test_openenv_env(tmp_path, env_name="test_env") | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| # Verify create_repo was called with default repo_id | |
| mock_api.create_repo.assert_called_once() | |
| call_args = mock_api.create_repo.call_args | |
| assert call_args.kwargs["repo_id"] == "testuser/test_env" | |
| def test_push_uses_private_option(tmp_path: Path) -> None: | |
| """Test that push respects --private option.""" | |
| _create_test_openenv_env(tmp_path) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push", "--private"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| # Verify create_repo was called with private=True | |
| mock_api.create_repo.assert_called_once() | |
| call_args = mock_api.create_repo.call_args | |
| assert call_args.kwargs["private"] is True | |
| def test_push_uses_base_image_option(tmp_path: Path) -> None: | |
| """Test that push respects --base-image option.""" | |
| _create_test_openenv_env(tmp_path) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push", "--base-image", "custom-base:latest"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| # Verify API was called (we can't easily test Dockerfile modification without reading staging dir) | |
| assert mock_api.upload_folder.called | |
| def test_push_uses_directory_argument(tmp_path: Path) -> None: | |
| """Test that push respects directory argument.""" | |
| env_dir = tmp_path / "my_env" | |
| env_dir.mkdir() | |
| _create_test_openenv_env(env_dir) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| # Directory is a positional argument, not an option | |
| result = runner.invoke( | |
| app, | |
| ["push", str(env_dir)], | |
| ) | |
| # Verify API was called | |
| assert mock_api.upload_folder.called | |
| def test_push_accepts_dockerfile_at_env_root(tmp_path: Path) -> None: | |
| """Test that push works when Dockerfile is at environment root instead of server/.""" | |
| _create_test_openenv_env(tmp_path) | |
| # Move Dockerfile from server/ to env root | |
| root_dockerfile = tmp_path / "Dockerfile" | |
| (tmp_path / "server" / "Dockerfile").rename(root_dockerfile) | |
| staged_files: list[list[str]] = [] | |
| def _capture_staging(*, folder_path: str, **_: object) -> None: | |
| staging = Path(folder_path) | |
| staged_files.append( | |
| sorted( | |
| str(p.relative_to(staging)) for p in staging.rglob("*") if p.is_file() | |
| ) | |
| ) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None | |
| mock_api = MagicMock() | |
| mock_api.upload_folder.side_effect = _capture_staging | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| assert result.exit_code == 0, result.output | |
| assert mock_api.upload_folder.called | |
| # Verify the staging directory has Dockerfile at root, not inside server/ | |
| files = staged_files[0] | |
| assert "Dockerfile" in files | |
| assert "server/Dockerfile" not in files | |
| def test_push_handles_missing_dockerfile(tmp_path: Path) -> None: | |
| """Test that push fails when Dockerfile is missing (required for deployment).""" | |
| _create_test_openenv_env(tmp_path) | |
| # Remove Dockerfile (no root Dockerfile either) | |
| (tmp_path / "server" / "Dockerfile").unlink() | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| # Dockerfile is now required - should fail | |
| assert result.exit_code != 0 | |
| assert "dockerfile" in result.output.lower() or "missing" in result.output.lower() | |
| def test_push_handles_missing_readme(tmp_path: Path) -> None: | |
| """Test that push fails when README.md is missing (required for deployment).""" | |
| _create_test_openenv_env(tmp_path) | |
| # Remove README | |
| (tmp_path / "README.md").unlink() | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| # README.md is now required - should fail | |
| assert result.exit_code != 0 | |
| assert "readme" in result.output.lower() or "missing" in result.output.lower() | |
| def test_push_initializes_hf_api_without_token(tmp_path: Path) -> None: | |
| """Test that push initializes HfApi without token parameter.""" | |
| _create_test_openenv_env(tmp_path) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| # Verify HfApi was initialized without token parameter | |
| mock_hf_api_class.assert_called_once() | |
| call_args = mock_hf_api_class.call_args | |
| # Should not have token in kwargs | |
| assert "token" not in (call_args.kwargs or {}) | |
| def test_push_validates_repo_id_format(tmp_path: Path) -> None: | |
| """Test that push validates repo-id format.""" | |
| _create_test_openenv_env(tmp_path) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| # Mock HfApi to prevent actual API calls | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| # Invalid format (no slash) | |
| result = runner.invoke(app, ["push", "--repo-id", "invalid-repo-id"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| assert result.exit_code != 0 | |
| assert "repo-id" in result.output.lower() or "format" in result.output.lower() | |
| def test_push_validates_manifest_is_dict(tmp_path: Path) -> None: | |
| """Test that push validates manifest is a dictionary.""" | |
| import yaml | |
| # Create complete env structure then overwrite openenv.yaml with non-dict | |
| _create_test_openenv_env(tmp_path) | |
| with open(tmp_path / "openenv.yaml", "w") as f: | |
| yaml.dump("not a dict", f) | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| assert result.exit_code != 0 | |
| assert "dictionary" in result.output.lower() or "yaml" in result.output.lower() | |
| def test_push_handles_whoami_object_return(tmp_path: Path) -> None: | |
| """Test that push handles whoami returning an object instead of dict.""" | |
| _create_test_openenv_env(tmp_path) | |
| # Create a mock object with name attribute | |
| class MockUser: | |
| def __init__(self): | |
| self.name = "testuser" | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = MockUser() | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| # Verify it worked with object return type | |
| assert mock_api.upload_folder.called | |
| def test_push_handles_authentication_failure(tmp_path: Path) -> None: | |
| """Test that push handles authentication failure.""" | |
| _create_test_openenv_env(tmp_path) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| # First whoami call fails (not authenticated) | |
| # Login also fails | |
| mock_whoami.side_effect = Exception("Not authenticated") | |
| mock_login.side_effect = Exception("Login failed") | |
| # Mock HfApi to prevent actual API calls | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| assert result.exit_code != 0 | |
| assert ( | |
| "authentication" in result.output.lower() | |
| or "login" in result.output.lower() | |
| ) | |
| def test_push_handles_whoami_missing_username(tmp_path: Path) -> None: | |
| """Test that push handles whoami response without username.""" | |
| _create_test_openenv_env(tmp_path) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| # Return dict without name, fullname, or username | |
| mock_whoami.return_value = {} | |
| # Mock login to prevent actual login prompt | |
| mock_login.return_value = None | |
| # Mock HfApi to prevent actual API calls | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| assert result.exit_code != 0 | |
| assert "username" in result.output.lower() or "extract" in result.output.lower() | |
| def test_push_handles_readme_without_frontmatter(tmp_path: Path) -> None: | |
| """Test that push handles README without frontmatter.""" | |
| _create_test_openenv_env(tmp_path) | |
| # Create README without frontmatter | |
| (tmp_path / "README.md").write_text("# Test Environment\nNo frontmatter here.\n") | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| # Verify it still works (should add frontmatter) | |
| assert mock_api.upload_folder.called | |
| def test_push_handles_hf_api_create_repo_error(tmp_path: Path) -> None: | |
| """Test that push handles HF API create_repo error.""" | |
| _create_test_openenv_env(tmp_path) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_api.create_repo.side_effect = Exception("API Error") | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| # Should continue despite error (warns but doesn't fail) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| # Should still attempt upload | |
| assert mock_api.upload_folder.called | |
| def test_push_handles_hf_api_upload_error(tmp_path: Path) -> None: | |
| """Test that push handles HF API upload_folder error.""" | |
| _create_test_openenv_env(tmp_path) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_api.upload_folder.side_effect = Exception("Upload failed") | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| assert result.exit_code != 0 | |
| assert "upload" in result.output.lower() or "failed" in result.output.lower() | |
| def test_push_handles_base_image_not_found_in_dockerfile(tmp_path: Path) -> None: | |
| """Test that push handles Dockerfile without FROM line.""" | |
| _create_test_openenv_env(tmp_path) | |
| # Create Dockerfile without FROM line | |
| (tmp_path / "server" / "Dockerfile").write_text( | |
| 'RUN echo \'test\'\nCMD ["echo", "test"]\n' | |
| ) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push", "--base-image", "custom-base:latest"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| # Should still work (adds FROM at beginning) | |
| assert mock_api.upload_folder.called | |
| def test_push_excludes_files_from_ignore_file(tmp_path: Path) -> None: | |
| """Test that push excludes files using patterns loaded via --exclude.""" | |
| _create_test_openenv_env(tmp_path) | |
| # Create files/folders to verify exclusion behavior. | |
| (tmp_path / "excluded_dir").mkdir() | |
| (tmp_path / "excluded_dir" / "secret.txt").write_text("do not upload") | |
| (tmp_path / "weights.bin").write_text("binary payload") | |
| (tmp_path / "keep.txt").write_text("keep me") | |
| ignore_file = tmp_path / ".openenvignore" | |
| ignore_file.write_text( | |
| """ | |
| # comments and empty lines are ignored | |
| excluded_dir/ | |
| *.bin | |
| """ | |
| ) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| def _assert_upload_payload(*_unused_args, **kwargs): | |
| ignore_patterns = kwargs["ignore_patterns"] | |
| assert "excluded_dir/" in ignore_patterns | |
| assert "*.bin" in ignore_patterns | |
| assert ".*" in ignore_patterns | |
| staged = Path(kwargs["folder_path"]) | |
| assert not (staged / "excluded_dir").exists() | |
| assert not (staged / "weights.bin").exists() | |
| assert (staged / "keep.txt").exists() | |
| mock_api.upload_folder.side_effect = _assert_upload_payload | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke( | |
| app, | |
| ["push", "--exclude", ".openenvignore"], | |
| ) | |
| finally: | |
| os.chdir(old_cwd) | |
| assert result.exit_code == 0 | |
| assert mock_api.upload_folder.called | |
| def test_push_does_not_use_gitignore_as_default_excludes(tmp_path: Path) -> None: | |
| """Test that .gitignore patterns are not used by default.""" | |
| _create_test_openenv_env(tmp_path) | |
| (tmp_path / ".gitignore").write_text("excluded_from_gitignore/\n") | |
| (tmp_path / "excluded_from_gitignore").mkdir() | |
| (tmp_path / "excluded_from_gitignore" / "secret.txt").write_text("upload me") | |
| (tmp_path / "keep.txt").write_text("keep me") | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| def _assert_upload_payload(*_unused_args, **kwargs): | |
| ignore_patterns = kwargs["ignore_patterns"] | |
| assert "excluded_from_gitignore/" not in ignore_patterns | |
| staged = Path(kwargs["folder_path"]) | |
| assert (staged / "excluded_from_gitignore").exists() | |
| assert (staged / "keep.txt").exists() | |
| mock_api.upload_folder.side_effect = _assert_upload_payload | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke(app, ["push"]) | |
| finally: | |
| os.chdir(old_cwd) | |
| assert result.exit_code == 0 | |
| assert mock_api.upload_folder.called | |
| def test_push_fails_when_exclude_file_missing(tmp_path: Path) -> None: | |
| """Test that push fails if --exclude points to a missing file.""" | |
| _create_test_openenv_env(tmp_path) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None # Prevent actual login prompt | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke( | |
| app, | |
| ["push", "--exclude", "missing.ignore"], | |
| ) | |
| finally: | |
| os.chdir(old_cwd) | |
| assert result.exit_code != 0 | |
| assert "exclude file" in result.output.lower() | |
| def test_push_create_pr_sets_upload_flag_and_skips_create_repo(tmp_path: Path) -> None: | |
| """Test that --create-pr uploads with PR mode and skips repo creation.""" | |
| _create_test_openenv_env(tmp_path) | |
| with ( | |
| patch("openenv.cli.commands.push.whoami") as mock_whoami, | |
| patch("openenv.cli.commands.push.login") as mock_login, | |
| patch("openenv.cli.commands.push.HfApi") as mock_hf_api_class, | |
| ): | |
| mock_whoami.return_value = {"name": "testuser"} | |
| mock_login.return_value = None | |
| mock_api = MagicMock() | |
| mock_hf_api_class.return_value = mock_api | |
| old_cwd = os.getcwd() | |
| try: | |
| os.chdir(str(tmp_path)) | |
| result = runner.invoke( | |
| app, ["push", "--repo-id", "my-org/my-env", "--create-pr"] | |
| ) | |
| finally: | |
| os.chdir(old_cwd) | |
| assert result.exit_code == 0 | |
| mock_api.upload_folder.assert_called_once() | |
| call_kwargs = mock_api.upload_folder.call_args[1] | |
| assert call_kwargs.get("create_pr") is True | |
| # When create_pr we do not create the repo (target repo must exist) | |
| mock_api.create_repo.assert_not_called() | |