Initial upload: ScaffoldForge CLI tool with full codebase, tests, and CI/CD
This commit is contained in:
296
tests/integration/test_full_workflow.py
Normal file
296
tests/integration/test_full_workflow.py
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
"""Integration tests for ScaffoldForge."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import Mock, patch, MagicMock
|
||||||
|
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
from scaffoldforge.cli import cli
|
||||||
|
from scaffoldforge.parsers import IssueParser, IssueData, ChecklistItem
|
||||||
|
from scaffoldforge.templates import TemplateEngine
|
||||||
|
from scaffoldforge.generators import StructureGenerator, CodeGenerator
|
||||||
|
|
||||||
|
|
||||||
|
class TestFullWorkflow:
|
||||||
|
"""Integration tests for the full workflow."""
|
||||||
|
|
||||||
|
@patch('scaffoldforge.cli.commands.IssueParser')
|
||||||
|
@patch('scaffoldforge.cli.commands.TemplateEngine')
|
||||||
|
@patch('scaffoldforge.cli.commands.StructureGenerator')
|
||||||
|
@patch('scaffoldforge.cli.commands.CodeGenerator')
|
||||||
|
def test_full_generate_workflow(
|
||||||
|
self, mock_code_gen, mock_struct_gen, mock_tpl_engine, mock_parser
|
||||||
|
):
|
||||||
|
"""Test complete generate workflow."""
|
||||||
|
runner = CliRunner()
|
||||||
|
|
||||||
|
mock_issue_data = Mock()
|
||||||
|
mock_issue_data.number = 42
|
||||||
|
mock_issue_data.title = "Implement User Authentication"
|
||||||
|
mock_issue_data.body = "Add user login and registration"
|
||||||
|
mock_issue_data.labels = ["python", "security"]
|
||||||
|
mock_issue_data.checklist = [
|
||||||
|
ChecklistItem(text="Create login form", completed=False),
|
||||||
|
ChecklistItem(text="Add password hashing", completed=False),
|
||||||
|
]
|
||||||
|
mock_issue_data.get_todo_items.return_value = [
|
||||||
|
"Create login form",
|
||||||
|
"Add password hashing"
|
||||||
|
]
|
||||||
|
mock_issue_data.requirements = ["Must be secure", "Must be fast"]
|
||||||
|
mock_issue_data.acceptance_criteria = []
|
||||||
|
mock_issue_data.suggested_files = []
|
||||||
|
mock_issue_data.suggested_directories = []
|
||||||
|
mock_issue_data.repository = "user/repo"
|
||||||
|
mock_issue_data.author = "testuser"
|
||||||
|
mock_issue_data.created_at = "2024-01-01T00:00:00"
|
||||||
|
|
||||||
|
mock_parser_instance = Mock()
|
||||||
|
mock_parser_instance.parse_issue.return_value = mock_issue_data
|
||||||
|
mock_parser_instance.detect_language.return_value = "python"
|
||||||
|
mock_parser.return_value = mock_parser_instance
|
||||||
|
|
||||||
|
mock_engine = Mock()
|
||||||
|
mock_engine.load_templates.return_value = {
|
||||||
|
"main.py": "# content",
|
||||||
|
"utils.py": "# utils"
|
||||||
|
}
|
||||||
|
mock_engine.get_template_context.return_value = {
|
||||||
|
"project_name": "implement-user-authentication",
|
||||||
|
"issue_number": 42
|
||||||
|
}
|
||||||
|
mock_tpl_engine.return_value = mock_engine
|
||||||
|
|
||||||
|
mock_code_gen_instance = Mock()
|
||||||
|
mock_code_gen_instance.generate_all_files.return_value = []
|
||||||
|
mock_code_gen.return_value = mock_code_gen_instance
|
||||||
|
|
||||||
|
mock_struct_gen_instance = Mock()
|
||||||
|
mock_struct_gen.return_value = mock_struct_gen_instance
|
||||||
|
|
||||||
|
with runner.isolated_filesystem():
|
||||||
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
[
|
||||||
|
"generate",
|
||||||
|
"https://github.com/user/repo/issues/42",
|
||||||
|
"--language", "python",
|
||||||
|
"--output", "./test-project"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
def test_template_engine_with_real_templates(self):
|
||||||
|
"""Test TemplateEngine with real built-in templates."""
|
||||||
|
engine = TemplateEngine()
|
||||||
|
|
||||||
|
languages = engine.list_available_languages()
|
||||||
|
assert "python" in languages
|
||||||
|
assert "javascript" in languages
|
||||||
|
assert "go" in languages
|
||||||
|
assert "rust" in languages
|
||||||
|
|
||||||
|
python_templates = engine.list_available_templates("python")
|
||||||
|
assert "default" in python_templates
|
||||||
|
|
||||||
|
def test_issue_data_integration(self):
|
||||||
|
"""Test IssueData with real checklist items."""
|
||||||
|
checklist = [
|
||||||
|
ChecklistItem(text="First task", completed=False),
|
||||||
|
ChecklistItem(text="Second task", completed=True),
|
||||||
|
ChecklistItem(text="Third task", completed=False),
|
||||||
|
]
|
||||||
|
|
||||||
|
issue_data = IssueData(
|
||||||
|
number=100,
|
||||||
|
title="Integration Test Issue",
|
||||||
|
body="Test body for integration",
|
||||||
|
body_html="<p>Test body</p>",
|
||||||
|
labels=["test"],
|
||||||
|
state="open",
|
||||||
|
url="https://github.com/test/repo/issues/100",
|
||||||
|
repository="test/repo",
|
||||||
|
author="testuser",
|
||||||
|
created_at="2024-01-01T00:00:00",
|
||||||
|
updated_at="2024-01-02T00:00:00",
|
||||||
|
checklist=checklist
|
||||||
|
)
|
||||||
|
|
||||||
|
todos = issue_data.get_todo_items()
|
||||||
|
completed = issue_data.get_completed_items()
|
||||||
|
|
||||||
|
assert len(todos) == 2
|
||||||
|
assert len(completed) == 1
|
||||||
|
assert "First task" in todos
|
||||||
|
assert "Second task" in completed
|
||||||
|
|
||||||
|
def test_generator_with_real_templates(self):
|
||||||
|
"""Test code generation with real templates."""
|
||||||
|
issue_data = IssueData(
|
||||||
|
number=1,
|
||||||
|
title="Test Python Project",
|
||||||
|
body="",
|
||||||
|
body_html="",
|
||||||
|
labels=["python"],
|
||||||
|
state="open",
|
||||||
|
url="",
|
||||||
|
repository="",
|
||||||
|
author="",
|
||||||
|
created_at="",
|
||||||
|
updated_at="",
|
||||||
|
checklist=[
|
||||||
|
ChecklistItem(text="Task 1", completed=False),
|
||||||
|
ChecklistItem(text="Task 2", completed=False),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
engine = TemplateEngine()
|
||||||
|
gen = CodeGenerator(engine, issue_data)
|
||||||
|
|
||||||
|
files = gen.generate_all_files("python", issue_data)
|
||||||
|
|
||||||
|
assert len(files) > 0
|
||||||
|
for f in files:
|
||||||
|
assert f.path is not None
|
||||||
|
assert f.content is not None
|
||||||
|
|
||||||
|
def test_structure_generator_preview_mode(self):
|
||||||
|
"""Test structure generator in preview mode."""
|
||||||
|
issue_data = IssueData(
|
||||||
|
number=1,
|
||||||
|
title="Preview Test",
|
||||||
|
body="",
|
||||||
|
body_html="",
|
||||||
|
labels=["python"],
|
||||||
|
state="open",
|
||||||
|
url="",
|
||||||
|
repository="",
|
||||||
|
author="",
|
||||||
|
created_at="",
|
||||||
|
updated_at="",
|
||||||
|
checklist=[]
|
||||||
|
)
|
||||||
|
|
||||||
|
engine = TemplateEngine()
|
||||||
|
code_gen = CodeGenerator(engine, issue_data)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
gen = StructureGenerator(output_dir=tmpdir, preview=True)
|
||||||
|
gen.generate("python", issue_data, code_gen)
|
||||||
|
|
||||||
|
assert len(gen.created_files) == 0
|
||||||
|
assert len(gen.created_dirs) == 0
|
||||||
|
|
||||||
|
def test_multiple_language_templates(self):
|
||||||
|
"""Test that all language templates can be loaded."""
|
||||||
|
engine = TemplateEngine()
|
||||||
|
|
||||||
|
for lang in ["python", "javascript", "go", "rust"]:
|
||||||
|
templates = engine.list_available_templates(lang)
|
||||||
|
assert len(templates) >= 1
|
||||||
|
|
||||||
|
context = engine.get_template_context(
|
||||||
|
IssueData(
|
||||||
|
number=1, title="Test", body="", body_html="",
|
||||||
|
labels=[lang], state="open", url="", repository="",
|
||||||
|
author="", created_at="", updated_at=""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert context is not None
|
||||||
|
|
||||||
|
def test_cli_with_preview_flag(self):
|
||||||
|
"""Test CLI with preview flag doesn't create files."""
|
||||||
|
runner = CliRunner()
|
||||||
|
|
||||||
|
with patch('scaffoldforge.cli.commands.IssueParser') as mock_parser, \
|
||||||
|
patch('scaffoldforge.cli.commands.TemplateEngine') as mock_tpl_engine, \
|
||||||
|
patch('scaffoldforge.cli.commands.StructureGenerator') as mock_struct_gen, \
|
||||||
|
patch('scaffoldforge.cli.commands.CodeGenerator') as mock_code_gen:
|
||||||
|
|
||||||
|
mock_issue_data = Mock()
|
||||||
|
mock_issue_data.number = 1
|
||||||
|
mock_issue_data.title = "Test"
|
||||||
|
mock_issue_data.labels = ["python"]
|
||||||
|
mock_issue_data.checklist = []
|
||||||
|
mock_issue_data.get_todo_items.return_value = []
|
||||||
|
mock_issue_data.requirements = []
|
||||||
|
mock_issue_data.body = ""
|
||||||
|
mock_issue_data.repository = ""
|
||||||
|
mock_issue_data.author = ""
|
||||||
|
mock_issue_data.created_at = ""
|
||||||
|
|
||||||
|
mock_parser.return_value.parse_issue.return_value = mock_issue_data
|
||||||
|
mock_parser.return_value.detect_language.return_value = "python"
|
||||||
|
|
||||||
|
with runner.isolated_filesystem():
|
||||||
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
["generate", "https://github.com/test/repo/issues/1", "--preview"]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
def test_readme_generation(self):
|
||||||
|
"""Test README generation in structure generator."""
|
||||||
|
issue_data = IssueData(
|
||||||
|
number=123,
|
||||||
|
title="Test Project",
|
||||||
|
body="This is a test project body",
|
||||||
|
body_html="",
|
||||||
|
labels=["python"],
|
||||||
|
state="open",
|
||||||
|
url="https://github.com/user/repo/issues/123",
|
||||||
|
repository="user/repo",
|
||||||
|
author="testuser",
|
||||||
|
created_at="2024-01-01T00:00:00",
|
||||||
|
updated_at="2024-01-02T00:00:00",
|
||||||
|
checklist=[
|
||||||
|
ChecklistItem(text="Task 1", completed=False),
|
||||||
|
ChecklistItem(text="Task 2", completed=True),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
engine = TemplateEngine()
|
||||||
|
code_gen = CodeGenerator(engine, issue_data)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
gen = StructureGenerator(output_dir=tmpdir, preview=False)
|
||||||
|
gen.generate("python", issue_data, code_gen)
|
||||||
|
|
||||||
|
readme_path = Path(tmpdir) / "test-project" / "README.md"
|
||||||
|
if readme_path.exists():
|
||||||
|
content = readme_path.read_text()
|
||||||
|
assert "Test Project" in content
|
||||||
|
assert "123" in content
|
||||||
|
|
||||||
|
|
||||||
|
class TestRepositoryIntegration:
|
||||||
|
"""Tests for repository integration features."""
|
||||||
|
|
||||||
|
def test_gitignore_content_by_language(self):
|
||||||
|
"""Test that gitignore content varies by language."""
|
||||||
|
issue_data = IssueData(
|
||||||
|
number=1, title="Test", body="", body_html="",
|
||||||
|
labels=[], state="open", url="", repository="",
|
||||||
|
author="", created_at="", updated_at=""
|
||||||
|
)
|
||||||
|
|
||||||
|
engine = TemplateEngine()
|
||||||
|
code_gen = CodeGenerator(engine, issue_data)
|
||||||
|
|
||||||
|
for lang in ["python", "javascript", "go", "rust"]:
|
||||||
|
files = code_gen.generate_all_files(lang, issue_data)
|
||||||
|
file_names = [f.path for f in files]
|
||||||
|
|
||||||
|
if lang == "python":
|
||||||
|
assert any("requirements" in f or "pyproject" in f for f in file_names)
|
||||||
|
elif lang == "javascript":
|
||||||
|
assert any("package.json" in f for f in file_names)
|
||||||
|
elif lang == "go":
|
||||||
|
assert any("go.mod" in f for f in file_names)
|
||||||
|
elif lang == "rust":
|
||||||
|
assert any("Cargo.toml" in f for f in file_names)
|
||||||
Reference in New Issue
Block a user