chore: enhance project initialization by adding post-task handling and improving file overwrite behavior
Some checks failed
CI / test (3.13) (push) Failing after 31s
Some checks failed
CI / test (3.13) (push) Failing after 31s
This commit is contained in:
@@ -21,7 +21,6 @@ from jinja2 import Environment, FileSystemLoader, StrictUndefined
|
||||
from quickbot_cli.cli import (
|
||||
_init_project,
|
||||
app,
|
||||
apply_optionals,
|
||||
ask_variables,
|
||||
init,
|
||||
load_template_spec,
|
||||
@@ -252,8 +251,8 @@ class TestRenderTree:
|
||||
assert output_file.exists()
|
||||
assert output_file.read_bytes() == binary_content
|
||||
|
||||
def test_render_tree_binary_file_exists_error(self, temp_dir: Path) -> None:
|
||||
"""Test that render_tree raises error when binary file exists and overwrite is disabled."""
|
||||
def test_render_tree_binary_file_existing_is_skipped(self, temp_dir: Path, mock_typer_secho: MagicMock) -> None:
|
||||
"""Existing binary file should be left untouched when overwrite is disabled."""
|
||||
template_root = temp_dir / "template"
|
||||
template_root.mkdir()
|
||||
|
||||
@@ -272,11 +271,18 @@ class TestRenderTree:
|
||||
context = {"project_name": "test_project"}
|
||||
env = Environment(loader=FileSystemLoader(str(template_root)), undefined=StrictUndefined, autoescape=True)
|
||||
|
||||
with pytest.raises(FileExistsError, match="File exists:"):
|
||||
render_tree(env, template_root, output_dir, context, overwrite=False)
|
||||
render_tree(env, template_root, output_dir, context, overwrite=False)
|
||||
# Should remain the existing content
|
||||
assert (output_dir / "image.png").read_bytes() == b"existing_binary_data"
|
||||
# Should show warning
|
||||
mock_typer_secho.assert_called_with(
|
||||
f"Warning: Skipping existing file: {output_dir / 'image.png'}", fg=typer.colors.YELLOW
|
||||
)
|
||||
|
||||
def test_render_tree_with_overwrite_disabled(self, temp_dir: Path) -> None:
|
||||
"""Test that render_tree raises error when overwrite is disabled and file exists."""
|
||||
def test_render_tree_with_overwrite_disabled_skips_existing(
|
||||
self, temp_dir: Path, mock_typer_secho: MagicMock
|
||||
) -> None:
|
||||
"""Existing text files should be skipped when overwrite is disabled."""
|
||||
template_root = temp_dir / "template"
|
||||
template_root.mkdir()
|
||||
|
||||
@@ -293,8 +299,13 @@ class TestRenderTree:
|
||||
context = {"project_name": "test_project"}
|
||||
env = Environment(loader=FileSystemLoader(str(template_root)), undefined=StrictUndefined, autoescape=True)
|
||||
|
||||
with pytest.raises(FileExistsError):
|
||||
render_tree(env, template_root, output_dir, context, overwrite=False)
|
||||
render_tree(env, template_root, output_dir, context, overwrite=False)
|
||||
# File content should remain unchanged
|
||||
assert (output_dir / "main.py").read_text() == "existing content"
|
||||
# Should show warning
|
||||
mock_typer_secho.assert_called_with(
|
||||
f"Warning: Skipping existing file: {output_dir / 'main.py'}", fg=typer.colors.YELLOW
|
||||
)
|
||||
|
||||
def test_render_tree_with_overwrite_enabled(self, temp_dir: Path) -> None:
|
||||
"""Test that render_tree overwrites existing files when enabled."""
|
||||
@@ -353,65 +364,77 @@ class TestRunPostTasks:
|
||||
# The actual error handling is tested in the main run_post_tasks function
|
||||
|
||||
|
||||
class TestApplyOptionals:
|
||||
"""Test optional module inclusion/exclusion."""
|
||||
|
||||
def test_apply_optionals_disables_alembic(self, temp_dir: Path) -> None:
|
||||
"""Test that apply_optionals removes alembic files when disabled."""
|
||||
# Create alembic files
|
||||
alembic_dir = temp_dir / "alembic"
|
||||
alembic_dir.mkdir()
|
||||
(alembic_dir / "alembic.ini").write_text("config")
|
||||
class TestPostTasksApplyOptionalsBehavior:
|
||||
"""Optional module inclusion/exclusion is applied via post_tasks now."""
|
||||
|
||||
def test_post_tasks_removes_alembic_when_disabled(self, temp_dir: Path) -> None:
|
||||
"""post_tasks should remove Alembic artifacts when disabled."""
|
||||
# Create structure
|
||||
(temp_dir / "alembic").mkdir()
|
||||
scripts_dir = temp_dir / "scripts"
|
||||
scripts_dir.mkdir()
|
||||
(scripts_dir / "migrations_generate.sh").write_text("script")
|
||||
(scripts_dir / "migrations_apply.sh").write_text("script")
|
||||
|
||||
apply_optionals(temp_dir, include_alembic=False, include_i18n=True)
|
||||
spec = {
|
||||
"post_tasks": [
|
||||
{
|
||||
"when": "{{ not include_alembic }}",
|
||||
"run": [
|
||||
"rm",
|
||||
"-rf",
|
||||
"alembic",
|
||||
"scripts/migrations_apply.sh",
|
||||
"scripts/migrations_generate.sh",
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
context = {"include_alembic": False}
|
||||
run_post_tasks(spec, context, temp_dir)
|
||||
|
||||
assert not alembic_dir.exists()
|
||||
assert not (temp_dir / "alembic").exists()
|
||||
assert not (scripts_dir / "migrations_generate.sh").exists()
|
||||
assert not (scripts_dir / "migrations_apply.sh").exists()
|
||||
|
||||
def test_apply_optionals_disables_babel(self, temp_dir: Path) -> None:
|
||||
"""Test that apply_optionals removes babel files when disabled."""
|
||||
# Create babel files
|
||||
locales_dir = temp_dir / "locales"
|
||||
locales_dir.mkdir()
|
||||
(locales_dir / "en").mkdir()
|
||||
|
||||
def test_post_tasks_removes_i18n_when_disabled(self, temp_dir: Path) -> None:
|
||||
"""post_tasks should remove i18n artifacts when disabled."""
|
||||
(temp_dir / "locales").mkdir()
|
||||
scripts_dir = temp_dir / "scripts"
|
||||
scripts_dir.mkdir()
|
||||
(scripts_dir / "babel_init.sh").write_text("script")
|
||||
(scripts_dir / "babel_extract.sh").write_text("script")
|
||||
(scripts_dir / "babel_update.sh").write_text("script")
|
||||
(scripts_dir / "babel_compile.sh").write_text("script")
|
||||
for f in [
|
||||
"babel_init.sh",
|
||||
"babel_extract.sh",
|
||||
"babel_update.sh",
|
||||
"babel_compile.sh",
|
||||
]:
|
||||
(scripts_dir / f).write_text("script")
|
||||
|
||||
apply_optionals(temp_dir, include_alembic=True, include_i18n=False)
|
||||
spec = {
|
||||
"post_tasks": [
|
||||
{
|
||||
"when": "{{ not include_i18n }}",
|
||||
"run": [
|
||||
"rm",
|
||||
"-rf",
|
||||
"locales",
|
||||
"scripts/babel_compile.sh",
|
||||
"scripts/babel_extract.sh",
|
||||
"scripts/babel_init.sh",
|
||||
"scripts/babel_update.sh",
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
context = {"include_i18n": False}
|
||||
run_post_tasks(spec, context, temp_dir)
|
||||
|
||||
assert not locales_dir.exists()
|
||||
assert not (temp_dir / "locales").exists()
|
||||
assert not (scripts_dir / "babel_init.sh").exists()
|
||||
assert not (scripts_dir / "babel_extract.sh").exists()
|
||||
assert not (scripts_dir / "babel_update.sh").exists()
|
||||
assert not (scripts_dir / "babel_compile.sh").exists()
|
||||
|
||||
def test_apply_optionals_keeps_enabled_modules(self, temp_dir: Path) -> None:
|
||||
"""Test that apply_optionals keeps files for enabled modules."""
|
||||
# Create both module files
|
||||
alembic_dir = temp_dir / "alembic"
|
||||
alembic_dir.mkdir()
|
||||
(alembic_dir / "alembic.ini").write_text("config")
|
||||
|
||||
locales_dir = temp_dir / "locales"
|
||||
locales_dir.mkdir()
|
||||
(locales_dir / "en").mkdir()
|
||||
|
||||
apply_optionals(temp_dir, include_alembic=True, include_i18n=True)
|
||||
|
||||
assert alembic_dir.exists()
|
||||
assert locales_dir.exists()
|
||||
|
||||
|
||||
class TestInitCommand:
|
||||
"""Test the main init command."""
|
||||
@@ -510,15 +533,70 @@ class TestInitCommand:
|
||||
output_path = temp_dir / "output"
|
||||
_init_project(output_path, "basic", overwrite=True)
|
||||
|
||||
def test_init_into_existing_dir_with_pyproject_is_ok_when_not_overwriting(self, temp_dir: Path) -> None:
|
||||
"""Regression: initializing into dir with existing pyproject.toml should skip it and proceed."""
|
||||
with patch("quickbot_cli.cli.TEMPLATES_DIR", temp_dir / "templates"):
|
||||
template_dir = temp_dir / "templates" / "basic"
|
||||
template_dir.mkdir(parents=True)
|
||||
|
||||
# Spec and minimal files including pyproject template
|
||||
(template_dir / "__template__.yaml").write_text(
|
||||
"variables:\n project_name:\n prompt: Project name\n default: test_project"
|
||||
)
|
||||
(template_dir / "app").mkdir()
|
||||
(template_dir / "app" / "main.py.j2").write_text("ok")
|
||||
(template_dir / "pyproject.toml.j2").write_text("[project]\nname='{{ project_name }}'")
|
||||
|
||||
# Prepare existing output with pyproject.toml
|
||||
output_path = temp_dir / "output"
|
||||
output_path.mkdir()
|
||||
(output_path / "pyproject.toml").write_text("[project]\nname='existing'")
|
||||
|
||||
# Should not raise, and should keep existing pyproject.toml
|
||||
_init_project(output_path, "basic", overwrite=False)
|
||||
assert (output_path / "pyproject.toml").read_text() == "[project]\nname='existing'"
|
||||
# New files should be generated
|
||||
assert (output_path / "app" / "main.py").exists()
|
||||
|
||||
def test_cli_boolean_flags_defaults_and_negation(self, temp_dir: Path) -> None:
|
||||
"""init() should honor boolean defaults and negation when called directly."""
|
||||
with patch("quickbot_cli.cli.TEMPLATES_DIR", temp_dir / "templates"):
|
||||
template_dir = temp_dir / "templates" / "basic"
|
||||
template_dir.mkdir(parents=True)
|
||||
|
||||
# Minimal spec and files
|
||||
# Spec with variables and post_tasks to remove disabled modules
|
||||
(template_dir / "__template__.yaml").write_text(
|
||||
"variables:\n project_name:\n prompt: P\n default: test_project\n"
|
||||
"""
|
||||
variables:
|
||||
project_name:
|
||||
prompt: P
|
||||
default: test_project
|
||||
include_alembic:
|
||||
prompt: A
|
||||
choices: [true, false]
|
||||
default: true
|
||||
include_i18n:
|
||||
prompt: I
|
||||
choices: [true, false]
|
||||
default: true
|
||||
post_tasks:
|
||||
- when: "{{ not include_alembic }}"
|
||||
run: [
|
||||
"rm","-rf",
|
||||
"alembic",
|
||||
"scripts/migrations_apply.sh",
|
||||
"scripts/migrations_generate.sh"
|
||||
]
|
||||
- when: "{{ not include_i18n }}"
|
||||
run: [
|
||||
"rm","-rf",
|
||||
"locales",
|
||||
"scripts/babel_compile.sh",
|
||||
"scripts/babel_extract.sh",
|
||||
"scripts/babel_init.sh",
|
||||
"scripts/babel_update.sh"
|
||||
]
|
||||
"""
|
||||
)
|
||||
(template_dir / "app").mkdir()
|
||||
(template_dir / "app" / "main.py.j2").write_text("ok")
|
||||
@@ -528,6 +606,11 @@ class TestInitCommand:
|
||||
(template_dir / "locales" / "en").mkdir(parents=True, exist_ok=True)
|
||||
(template_dir / "scripts").mkdir()
|
||||
(template_dir / "scripts" / "babel_init.sh.j2").write_text("b")
|
||||
(template_dir / "scripts" / "babel_extract.sh.j2").write_text("b")
|
||||
(template_dir / "scripts" / "babel_update.sh.j2").write_text("b")
|
||||
(template_dir / "scripts" / "babel_compile.sh.j2").write_text("b")
|
||||
(template_dir / "scripts" / "migrations_apply.sh.j2").write_text("b")
|
||||
(template_dir / "scripts" / "migrations_generate.sh.j2").write_text("b")
|
||||
|
||||
# Default (both enabled)
|
||||
out1 = temp_dir / "out1"
|
||||
@@ -557,7 +640,8 @@ class TestCLIHelp:
|
||||
result = cli_runner.invoke(app, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
# Check for the actual help text that appears
|
||||
assert "init [OPTIONS] OUTPUT" in result.output
|
||||
assert "init" in result.output
|
||||
assert "version" in result.output
|
||||
|
||||
def test_init_command_help(self, cli_runner: CliRunner) -> None:
|
||||
"""Test that init command shows help information."""
|
||||
@@ -565,15 +649,15 @@ class TestCLIHelp:
|
||||
result = cli_runner.invoke(app, ["init", "--help"])
|
||||
assert result.exit_code == 0
|
||||
# Check for the actual help text that appears
|
||||
assert "OUTPUT" in result.output
|
||||
assert "PATH" in result.output
|
||||
assert "--output" in result.output
|
||||
assert "Output directory" in result.output
|
||||
|
||||
def test_init_command_arguments(self, cli_runner: CliRunner) -> None:
|
||||
"""Test that init command accepts required arguments."""
|
||||
# Test the actual CLI interface
|
||||
result = cli_runner.invoke(app, ["init", "--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "OUTPUT" in result.output
|
||||
assert "--output" in result.output
|
||||
|
||||
def test_cli_wrapper_function(self) -> None:
|
||||
"""Test that the CLI wrapper function exists and is callable."""
|
||||
|
||||
@@ -20,7 +20,6 @@ import jinja2
|
||||
from jinja2 import Environment, FileSystemLoader, StrictUndefined
|
||||
|
||||
from quickbot_cli.cli import (
|
||||
apply_optionals,
|
||||
ask_variables,
|
||||
load_template_spec,
|
||||
render_tree,
|
||||
@@ -237,38 +236,71 @@ class TestEdgeCases:
|
||||
|
||||
mock_subprocess_run.assert_not_called()
|
||||
|
||||
def test_apply_optionals_with_missing_directories(self, temp_dir: Path) -> None:
|
||||
"""Test apply_optionals with missing directories."""
|
||||
# Don't create any directories, just test the function
|
||||
apply_optionals(temp_dir, include_alembic=False, include_i18n=False)
|
||||
|
||||
# Should not raise any errors
|
||||
def test_post_tasks_with_missing_directories(self, temp_dir: Path) -> None:
|
||||
"""Post tasks should tolerate missing directories and files."""
|
||||
spec = {
|
||||
"post_tasks": [
|
||||
{
|
||||
"when": "{{ not include_alembic }}",
|
||||
"run": [
|
||||
"rm",
|
||||
"-rf",
|
||||
"alembic",
|
||||
"scripts/migrations_apply.sh",
|
||||
"scripts/migrations_generate.sh",
|
||||
],
|
||||
},
|
||||
{
|
||||
"when": "{{ not include_i18n }}",
|
||||
"run": [
|
||||
"rm",
|
||||
"-rf",
|
||||
"locales",
|
||||
"scripts/babel_compile.sh",
|
||||
"scripts/babel_extract.sh",
|
||||
"scripts/babel_init.sh",
|
||||
"scripts/babel_update.sh",
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
run_post_tasks(spec, {"include_alembic": False, "include_i18n": False}, temp_dir)
|
||||
assert True
|
||||
|
||||
def test_apply_optionals_with_partial_structure(self, temp_dir: Path) -> None:
|
||||
"""Test apply_optionals with partial directory structure."""
|
||||
# Create only some of the expected directories
|
||||
def test_post_tasks_with_partial_structure(self, temp_dir: Path) -> None:
|
||||
"""Post tasks should handle partial directory structure."""
|
||||
(temp_dir / "alembic").mkdir()
|
||||
(temp_dir / "scripts").mkdir()
|
||||
(temp_dir / "scripts" / "migrations_generate.sh").write_text("script")
|
||||
|
||||
# Don't create locales or babel scripts
|
||||
|
||||
apply_optionals(temp_dir, include_alembic=False, include_i18n=True)
|
||||
|
||||
# Alembic should be removed
|
||||
spec = {
|
||||
"post_tasks": [
|
||||
{
|
||||
"when": "{{ not include_alembic }}",
|
||||
"run": [
|
||||
"rm",
|
||||
"-rf",
|
||||
"alembic",
|
||||
"scripts/migrations_generate.sh",
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
run_post_tasks(spec, {"include_alembic": False}, temp_dir)
|
||||
assert not (temp_dir / "alembic").exists()
|
||||
assert not (temp_dir / "scripts" / "migrations_generate.sh").exists()
|
||||
|
||||
def test_apply_optionals_with_files_instead_of_directories(self, temp_dir: Path) -> None:
|
||||
"""Test apply_optionals with files instead of directories."""
|
||||
# Create files with names that match expected directories
|
||||
def test_post_tasks_with_files_instead_of_directories(self, temp_dir: Path) -> None:
|
||||
"""Post tasks should remove files named like directories as well."""
|
||||
(temp_dir / "alembic").write_text("not a directory")
|
||||
(temp_dir / "locales").write_text("not a directory")
|
||||
|
||||
apply_optionals(temp_dir, include_alembic=False, include_i18n=False)
|
||||
|
||||
# Files should be removed
|
||||
spec = {
|
||||
"post_tasks": [
|
||||
{"when": "{{ not include_alembic }}", "run": ["rm", "-rf", "alembic"]},
|
||||
{"when": "{{ not include_i18n }}", "run": ["rm", "-rf", "locales"]},
|
||||
]
|
||||
}
|
||||
run_post_tasks(spec, {"include_alembic": False, "include_i18n": False}, temp_dir)
|
||||
assert not (temp_dir / "alembic").exists()
|
||||
assert not (temp_dir / "locales").exists()
|
||||
|
||||
@@ -302,12 +334,14 @@ class TestErrorHandling:
|
||||
with pytest.raises((SystemExit, Exception)):
|
||||
ask_variables(spec, non_interactive)
|
||||
|
||||
def test_render_tree_with_file_exists_error(self, temp_dir: Path) -> None:
|
||||
"""Test that render_tree raises FileExistsError when overwrite is disabled."""
|
||||
def test_render_tree_skips_existing_when_overwrite_disabled(
|
||||
self, temp_dir: Path, mock_typer_secho: MagicMock
|
||||
) -> None:
|
||||
"""Existing files are skipped (not overwritten) when overwrite is disabled."""
|
||||
template_root = temp_dir / "template"
|
||||
template_root.mkdir()
|
||||
|
||||
template_file = template_root / "main.py.j2"
|
||||
template_file = template_root / "main.py"
|
||||
template_file.write_text("app = FastAPI(title='{{ project_name }}')")
|
||||
|
||||
output_dir = temp_dir / "output"
|
||||
@@ -320,8 +354,13 @@ class TestErrorHandling:
|
||||
context: dict[str, Any] = {"project_name": "test"}
|
||||
env = Environment(autoescape=True)
|
||||
|
||||
with pytest.raises(FileExistsError):
|
||||
render_tree(env, template_root, output_dir, context, overwrite=False)
|
||||
render_tree(env, template_root, output_dir, context, overwrite=False)
|
||||
# Ensure original content remains
|
||||
assert (output_dir / "main.py").read_text() == "existing content"
|
||||
# Should show warning
|
||||
mock_typer_secho.assert_called_with(
|
||||
f"Warning: Skipping existing file: {output_dir / 'main.py'}", fg=typer.colors.YELLOW
|
||||
)
|
||||
|
||||
def test_render_tree_with_jinja_render_error(self, temp_dir: Path, mock_typer_secho: MagicMock) -> None:
|
||||
"""Test that render_tree logs and re-raises when Jinja rendering fails."""
|
||||
@@ -418,23 +457,23 @@ class TestBoundaryConditions:
|
||||
)
|
||||
assert deep_file.exists()
|
||||
|
||||
def test_apply_optionals_with_many_files(self, temp_dir: Path) -> None:
|
||||
"""Test apply_optionals with many files to process."""
|
||||
# Create many files
|
||||
def test_post_tasks_with_many_files(self, temp_dir: Path) -> None:
|
||||
"""Post tasks should remove only specific optional module directories."""
|
||||
for i in range(100):
|
||||
(temp_dir / f"file_{i}.txt").write_text(f"content {i}")
|
||||
|
||||
# Create the expected structure
|
||||
(temp_dir / "alembic").mkdir()
|
||||
(temp_dir / "locales").mkdir()
|
||||
|
||||
# Should not raise any errors
|
||||
apply_optionals(temp_dir, include_alembic=False, include_i18n=False)
|
||||
|
||||
# Only the specific optional modules should be removed, not the random files
|
||||
spec = {
|
||||
"post_tasks": [
|
||||
{"when": "{{ not include_alembic }}", "run": ["rm", "-rf", "alembic"]},
|
||||
{"when": "{{ not include_i18n }}", "run": ["rm", "-rf", "locales"]},
|
||||
]
|
||||
}
|
||||
run_post_tasks(spec, {"include_alembic": False, "include_i18n": False}, temp_dir)
|
||||
assert not (temp_dir / "alembic").exists()
|
||||
assert not (temp_dir / "locales").exists()
|
||||
# Random files should still exist
|
||||
assert (temp_dir / "file_0.txt").exists()
|
||||
assert (temp_dir / "file_50.txt").exists()
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
# pytest is used by the test framework
|
||||
import typer
|
||||
|
||||
from quickbot_cli.cli import _init_project
|
||||
|
||||
@@ -203,9 +204,33 @@ class TestCLIIntegration:
|
||||
spec_content = {
|
||||
"variables": {
|
||||
"project_name": {"prompt": "Project name", "default": "simple_bot"},
|
||||
"include_alembic": {"prompt": "Include Alembic?", "choices": ["yes", "no"], "default": "no"},
|
||||
"include_i18n": {"prompt": "Include i18n?", "choices": ["yes", "no"], "default": "no"},
|
||||
}
|
||||
"include_alembic": {"prompt": "Include Alembic?", "choices": [True, False], "default": False},
|
||||
"include_i18n": {"prompt": "Include i18n?", "choices": [True, False], "default": False},
|
||||
},
|
||||
"post_tasks": [
|
||||
{
|
||||
"when": "{{ not include_alembic }}",
|
||||
"run": [
|
||||
"rm",
|
||||
"-rf",
|
||||
"alembic",
|
||||
"scripts/migrations_generate.sh",
|
||||
"scripts/migrations_apply.sh",
|
||||
],
|
||||
},
|
||||
{
|
||||
"when": "{{ not include_i18n }}",
|
||||
"run": [
|
||||
"rm",
|
||||
"-rf",
|
||||
"locales",
|
||||
"scripts/babel_init.sh",
|
||||
"scripts/babel_extract.sh",
|
||||
"scripts/babel_update.sh",
|
||||
"scripts/babel_compile.sh",
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
spec_file = template_dir / "__template__.yaml"
|
||||
@@ -283,8 +308,8 @@ class TestCLIIntegration:
|
||||
assert "app = FastAPI(title='overwrite_test')" in (output_dir / "app" / "main.py").read_text()
|
||||
assert "existing content" not in (output_dir / "app" / "main.py").read_text()
|
||||
|
||||
def test_project_generation_without_overwrite_fails(self) -> None:
|
||||
"""Test that project generation fails without overwrite when files exist."""
|
||||
def test_project_generation_without_overwrite_skips_existing(self) -> None:
|
||||
"""Project generation should skip existing files when overwrite is False."""
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
tmp_path = Path(tmp_dir)
|
||||
|
||||
@@ -310,9 +335,12 @@ class TestCLIIntegration:
|
||||
existing_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
existing_file.write_text("existing content")
|
||||
|
||||
# Should fail with FileExistsError when overwrite is False
|
||||
with pytest.raises(FileExistsError):
|
||||
# Should complete and keep the existing file content
|
||||
with patch("quickbot_cli.cli.typer.secho") as mock_secho:
|
||||
_init_project(output_dir, "basic", project_name="overwrite_test")
|
||||
# Should show warning about skipping existing file
|
||||
expected_warning = f"Warning: Skipping existing file: {output_dir / 'app' / 'main.py'}"
|
||||
mock_secho.assert_any_call(expected_warning, fg=typer.colors.YELLOW)
|
||||
|
||||
# Check that file was not overwritten
|
||||
assert "existing content" in (output_dir / "app" / "main.py").read_text()
|
||||
|
||||
Reference in New Issue
Block a user