Skip to content

Commit 695f937

Browse files
committed
Enhance test coverage and configuration for OpenEnv CLI
- Added coverage configuration to `pyproject.toml` to exclude specific files and lines from coverage reports. - Introduced new tests in `test_init.py` to validate environment name requirements and handle file path collisions. - Created `test_main.py` to ensure the main application handles exceptions and can be invoked as an entry point. - Expanded `test_push.py` with additional tests for authentication handling, error scenarios, and validation of input formats.
1 parent f9cd98a commit 695f937

File tree

4 files changed

+381
-1
lines changed

4 files changed

+381
-1
lines changed

pyproject.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,20 @@ package-dir = {"" = "src"}
2727

2828
[tool.setuptools.packages.find]
2929
where = ["src"]
30+
31+
[tool.coverage.run]
32+
omit = [
33+
"openenv_cli/templates/**",
34+
"**/templates/**",
35+
"openenv_cli/__main__.py",
36+
]
37+
38+
[tool.coverage.report]
39+
exclude_lines = [
40+
"pragma: no cover",
41+
"def __repr__",
42+
"raise AssertionError",
43+
"raise NotImplementedError",
44+
"if __name__ == .__main__.:",
45+
"if TYPE_CHECKING:",
46+
]

tests/test_cli/test_init.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,3 +362,82 @@ def test_init_requirements_file(tmp_path: Path) -> None:
362362
assert "fastapi" in req_content
363363
assert "uvicorn" in req_content
364364
assert "openenv-core>=0.1.0" in req_content
365+
366+
367+
def test_init_validates_empty_env_name(tmp_path: Path) -> None:
368+
"""Test that init validates empty environment name."""
369+
old_cwd = os.getcwd()
370+
try:
371+
os.chdir(str(tmp_path))
372+
result = runner.invoke(app, ["init", ""], input="\n")
373+
finally:
374+
os.chdir(old_cwd)
375+
376+
assert result.exit_code != 0
377+
assert "cannot be empty" in result.output.lower()
378+
379+
380+
def test_init_env_name_without_env_suffix(tmp_path: Path) -> None:
381+
"""Test that init works with env names that don't end with _env."""
382+
env_name = "mygame" # No _env suffix
383+
env_dir = tmp_path / env_name
384+
385+
old_cwd = os.getcwd()
386+
try:
387+
os.chdir(str(tmp_path))
388+
result = runner.invoke(app, ["init", env_name], input="\n")
389+
finally:
390+
os.chdir(old_cwd)
391+
392+
assert result.exit_code == 0
393+
assert env_dir.exists()
394+
395+
# Check that prefix is correctly derived (should be "Mygame" for "mygame")
396+
models_content = (env_dir / "models.py").read_text()
397+
assert "MygameAction" in models_content or "Mygame" in models_content
398+
399+
400+
def test_init_single_part_env_name(tmp_path: Path) -> None:
401+
"""Test that init works with single-part env names."""
402+
env_name = "game" # Single part, no underscores
403+
env_dir = tmp_path / env_name
404+
405+
old_cwd = os.getcwd()
406+
try:
407+
os.chdir(str(tmp_path))
408+
result = runner.invoke(app, ["init", env_name], input="\n")
409+
finally:
410+
os.chdir(old_cwd)
411+
412+
assert result.exit_code == 0
413+
assert env_dir.exists()
414+
415+
416+
def test_init_handles_file_path_collision(tmp_path: Path) -> None:
417+
"""Test that init fails when path exists as a file."""
418+
env_name = "existing_file"
419+
file_path = tmp_path / env_name
420+
file_path.write_text("existing file content")
421+
422+
old_cwd = os.getcwd()
423+
try:
424+
os.chdir(str(tmp_path))
425+
result = runner.invoke(app, ["init", env_name], input="\n")
426+
finally:
427+
os.chdir(old_cwd)
428+
429+
# The command should fail with exit code 2 (typer bad parameter)
430+
assert result.exit_code != 0, f"Expected command to fail, but it succeeded. Output: {result.output}"
431+
# Check that it's a BadParameter error (exit code 2) and not just a usage error
432+
# Typer formats BadParameter errors in the Error section
433+
error_output = result.output.lower()
434+
# The error message should mention the path or file, or at least indicate an error
435+
# Exit code 2 indicates BadParameter, and "error" in output indicates it's an error
436+
assert (
437+
result.exit_code == 2 or # BadParameter exit code
438+
"error" in error_output or
439+
"exists" in error_output or
440+
"file" in error_output or
441+
str(file_path).lower() in error_output or
442+
env_name.lower() in error_output
443+
), f"Expected BadParameter error about file collision. Exit code: {result.exit_code}, Output: {result.output}"

tests/test_cli/test_main.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
"""Tests for the openenv __main__ module."""
8+
9+
import sys
10+
from unittest.mock import patch
11+
12+
import pytest
13+
from typer.testing import CliRunner
14+
15+
from openenv_cli.__main__ import app, main
16+
17+
18+
runner = CliRunner()
19+
20+
21+
def test_main_handles_keyboard_interrupt() -> None:
22+
"""Test that main handles KeyboardInterrupt gracefully."""
23+
with patch("openenv_cli.__main__.app") as mock_app:
24+
mock_app.side_effect = KeyboardInterrupt()
25+
26+
with pytest.raises(SystemExit) as exc_info:
27+
main()
28+
29+
assert exc_info.value.code == 130
30+
31+
32+
def test_main_handles_generic_exception() -> None:
33+
"""Test that main handles generic exceptions gracefully."""
34+
with patch("openenv_cli.__main__.app") as mock_app:
35+
mock_app.side_effect = ValueError("Test error")
36+
37+
with pytest.raises(SystemExit) as exc_info:
38+
main()
39+
40+
assert exc_info.value.code == 1
41+
42+
43+
def test_main_entry_point() -> None:
44+
"""Test that main() can be called as entry point."""
45+
# This tests the if __name__ == "__main__" block indirectly
46+
# by ensuring main() function works
47+
with patch("openenv_cli.__main__.app") as mock_app:
48+
main()
49+
mock_app.assert_called_once()
50+

0 commit comments

Comments
 (0)