Skip to content

Commit

Permalink
Add test suite for CLI and enhance TODO tags
Browse files Browse the repository at this point in the history
Introduced a comprehensive test suite for the CLI functionality using `pytest` and `unittest.mock.patch` to ensure robustness. Enhanced TODO tags with issue numbers for improved tracking and organization.
  • Loading branch information
coordt committed Nov 18, 2024
1 parent a6fab99 commit 0296f46
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 11 deletions.
5 changes: 3 additions & 2 deletions project_forge/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ def build(

initial_context: dict[str, Any] = {}
if data_file:
initial_context |= parse_file(data_file)
values = parse_file(data_file)
initial_context |= values or {}

if data:
initial_context |= dict(data)
print(type(output_dir))

build_project(composition, output_dir=output_dir, use_defaults=use_defaults, initial_context=initial_context)
2 changes: 1 addition & 1 deletion project_forge/configurations/pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class Question(BaseModel):
),
)

# TODO: How to do a basic regex or other string pattern validation?
# TODO[#6]: How to do a basic regex or other string pattern validation?

validator: str = Field(
default="",
Expand Down
4 changes: 2 additions & 2 deletions project_forge/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def parse_git_path(path: str) -> PathInfo:
return {**default, **match_dict, **internal_path} # type: ignore[typeddict-item]


# TODO: Add abbreviation support such as `gh:` and `gl:`
# TODO[#4]: Add abbreviation support such as `gh:` and `gl:`


def parse_git_url(git_url: str) -> ParsedURL:
Expand Down Expand Up @@ -141,7 +141,7 @@ def parse_git_url(git_url: str) -> ParsedURL:
else:
url = ParsedURL(protocol="file", full_path=bits.path.rstrip("/"))

# TODO: parse_git_path won't work against a file:// scheme. The owner/repo_name will be in different places
# TODO[#5]: parse_git_path won't work against a file:// scheme. The owner/repo_name will be in different places
parsed_path = parse_git_path(url.full_path)
url.owner = parsed_path.get("owner")
url.repo_name = parsed_path.get("repo_name")
Expand Down
2 changes: 1 addition & 1 deletion project_forge/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ class Settings(BaseSettings):

def get_settings(config_file: Path = DEFAULT_CONFIG_FILE) -> Settings:
"""Return the settings."""
# TODO: Implement settings management
# TODO[#3]: Implement settings management
return Settings()
96 changes: 96 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from pathlib import Path
from unittest.mock import patch

import pytest
from project_forge.cli import build
from click.testing import CliRunner


@pytest.fixture
def runner():
"""The CLI runner."""
return CliRunner()


@pytest.fixture
def composition_path(tmp_path: Path):
"""The path to a composition file."""
path = tmp_path / "composition.yaml"
path.touch(exist_ok=True)
return path


class TestBuildCommand:
"""
Tests for the cli.build command.
Doesn't test the implementation, just the CLI interface.
"""

@patch("project_forge.commands.build.build_project")
def test_use_defaults_passed_to_build_project(self, mock_build_project, runner: CliRunner, composition_path: Path):
"""The `use-defaults` flag is correctly passed to the `build_project` function."""

result = runner.invoke(build, [str(composition_path), "--use-defaults"])

if result.exit_code != 0:
print(result.output)

assert result.exit_code == 0
mock_build_project.assert_called_once_with(
composition_path, output_dir=Path.cwd(), use_defaults=True, initial_context={}
)

@patch("project_forge.commands.build.build_project")
def test_custom_output_dir_passed_to_build_project(
self, mock_build_project, runner: CliRunner, composition_path: Path, tmp_path: Path
):
"""The `output-dir` option is correctly passed to the `build_project` function.`"""
output_dir = tmp_path / "custom-dir"
output_dir.mkdir(parents=True, exist_ok=True)

result = runner.invoke(build, [str(composition_path), "--output-dir", str(output_dir)])

if result.exit_code != 0:
print(result.output)

assert result.exit_code == 0
mock_build_project.assert_called_once_with(
composition_path, output_dir=output_dir, use_defaults=False, initial_context={}
)

@patch("project_forge.commands.build.build_project")
@patch("project_forge.cli.parse_file")
def test_data_file_adds_to_initial_context(
self, mock_parse_file, mock_build_project, runner: CliRunner, composition_path: Path, tmp_path: Path
):
"""The parsed results of a data file are added to the initial context."""
data_file_path = tmp_path / "data.yaml"
data_file_path.touch(exist_ok=True)
mock_parse_file.return_value = {"key": "value"}

result = runner.invoke(build, [str(composition_path), "--data-file", str(data_file_path)])

if result.exit_code != 0:
print("OUTPUT:", result.output, result)

assert result.exit_code == 0

mock_parse_file.assert_called_once_with(data_file_path)
mock_build_project.assert_called_once_with(
composition_path, output_dir=Path.cwd(), use_defaults=False, initial_context={"key": "value"}
)

@patch("project_forge.commands.build.build_project")
def test_data_options_adds_to_initial_context(self, mock_build_project, runner: CliRunner, composition_path: Path):
"""Data options are added to the initial context."""

result = runner.invoke(build, [str(composition_path), "-d", "key", "value"])

if result.exit_code != 0:
print(result.output)

assert result.exit_code == 0
mock_build_project.assert_called_once_with(
composition_path, output_dir=Path.cwd(), use_defaults=False, initial_context={"key": "value"}
)
9 changes: 4 additions & 5 deletions tests/test_configurations/test_composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,7 @@ def test_reads_and_converts_a_pattern_file(self, fixtures_dir: Path):
assert pattern.template_location.resolve() == fixtures_dir / "mkdocs" / "{{ repo_name }}"


# TODO: Composition scenarios to test
# - patterns with optional questions
# - patterns with optional questions that are answered in a previous pattern
# - patterns with files/directories named with mapped keys. They should properly get mapped to the combined template structure
# - patterns with mixed file types
# TODO[#7]: Write composition test scenario patterns with optional questions
# TODO[#8]: Write composition test scenario patterns with optional questions that are answered in a previous pattern
# TODO[#9]: Write composition test scenario patterns with files/directories named with mapped keys. They should properly get mapped to the combined template structure
# TODO[#10]: Write composition test scenario patterns with mixed file types

0 comments on commit 0296f46

Please sign in to comment.