Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
cbc0b13
Update issue templates
not-sure-ksharma12 Feb 10, 2026
8db8a39
Add GitHub Actions CI workflow for automated testing
not-sure-ksharma12 Feb 27, 2026
0962e2f
Add comprehensive CLI entry point tests (14 tests, 0% -> 63% coverage)
not-sure-ksharma12 Feb 27, 2026
d8bad85
Add feature-flag and placeholder replacement tests (22 tests)
not-sure-ksharma12 Feb 27, 2026
abec3ec
Add parameterized generation tests for all 18 template types (61 tests)
not-sure-ksharma12 Feb 27, 2026
6114340
Harden _update_file_content against path traversal (S2083) (#25)
not-sure-ksharma12 Apr 2, 2026
100a3fa
P2/issue 19 fix path injection (#26)
not-sure-ksharma12 Apr 2, 2026
73dc048
Fix bare except in create_icon font load (python:S5754) (#27)
not-sure-ksharma12 Apr 2, 2026
5d90616
Refactor update_template_info for lower cognitive complexity (#28)
not-sure-ksharma12 Apr 2, 2026
85150e6
Refactor on_preview for lower cognitive complexity (#29)
not-sure-ksharma12 Apr 2, 2026
f737e61
Remove duplicate setup_logging definition (#30)
not-sure-ksharma12 Apr 2, 2026
6705e95
Pilot builtin template registry for django-web-app (#24) (#31)
not-sure-ksharma12 Apr 2, 2026
3c18602
feat: inject template cache dir for TemplateManager (DI) (#38)
not-sure-ksharma12 May 9, 2026
78ae63b
refactor: centralize feature removal in policy table (#39)
not-sure-ksharma12 May 9, 2026
36d08e3
fix: import Union for cache path typing (#40)
not-sure-ksharma12 May 9, 2026
79c1695
refactor: use parameter object in generation flow
not-sure-ksharma12 May 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/ISSUE_TEMPLATE/custom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
name: Custom issue template
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''

---


49 changes: 49 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-cov pytest-mock
pip install -e ".[dev]" --no-deps || true
pip install -e . --no-deps || true

- name: Run tests with coverage
run: |
pytest tests/ \
--cov=python_project_generator \
--cov-report=xml:coverage.xml \
--cov-report=term-missing \
-v
env:
PYTHONPATH: src

- name: Upload coverage artifact
if: matrix.python-version == '3.12'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.xml
retention-days: 30
21 changes: 21 additions & 0 deletions docs/reviews/PR-1-self-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Self-Review: PR #1 — CI/CD Pipeline

## What changed and why?

Added a GitHub Actions CI workflow (`.github/workflows/ci.yml`) that automatically runs the test suite on every push to `main`, every pull request, and on manual dispatch. This is the foundation for regression protection — without CI, all test improvements are only useful when developers remember to run them locally.

## Why is this the right test layer (unit/integration/UI)?

This is **infrastructure**, not a test layer itself. It ensures that all tests (unit, integration, and future additions) are executed automatically, catching regressions before they reach `main`. The workflow uses a matrix strategy across Python 3.8–3.12 to verify cross-version compatibility.

## What could still break / what's not covered?

- **wxPython GUI tests** are not run in CI because wxPython requires a display server. GUI tests that mock wx properly will still pass, but any test requiring an actual display will need `xvfb` or similar.
- **OS matrix** is limited to Ubuntu. macOS and Windows runners could be added but would increase CI cost and time.
- **Coverage threshold** is not enforced — the workflow reports coverage but does not fail on a drop. This could be added as a follow-up.

## What risks or follow-ups remain?

- Consider adding a coverage threshold gate (e.g., fail if coverage drops below 30%).
- Consider adding macOS/Windows to the matrix once the test suite is more mature.
- A status badge could be added to `README.md` once the workflow is verified green.
23 changes: 23 additions & 0 deletions docs/reviews/PR-2-self-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Self-Review: PR #2 — CLI Interface Tests

## What changed and why?

Added `tests/test_cli.py` with 14 tests covering the `__main__.py` CLI entry point. This file previously had **0% test coverage** — the highest-priority gap in the codebase. Tests cover argument parsing (`--version`, `--help`, `--cli`, `--gui`), flag routing to the correct modules, argument forwarding, and error handling when wxPython is unavailable.

## Why is this the right test layer (unit/integration/UI)?

These are **unit tests** with mocked dependencies. The `__main__.py` module is a thin dispatcher — it parses arguments and delegates to either the GUI or CLI module. Unit tests with mocks are the right approach because:
- We don't want tests to depend on wxPython being installed
- We don't want tests to actually generate projects (that's integration-level)
- We need fast, deterministic verification of routing logic

## What could still break / what's not covered?

- The actual `from . import project_generator` import failure path is hard to test because Python's import machinery caches modules aggressively. The defensive fallback (`import project_generator as cli_mod`) is effectively untestable without subprocess isolation.
- The GUI launch path (`app.MainLoop()`) is not tested beyond verifying that a missing `wx` returns exit code 1.
- Coverage is 63% rather than the 80% target — the remaining 37% is the GUI launch path which requires wxPython.

## What risks or follow-ups remain?

- GUI integration tests (Issue #1) would cover the remaining `__main__.py` paths.
- Consider using `subprocess.run` to test `python -m python_project_generator --version` end-to-end.
28 changes: 28 additions & 0 deletions docs/reviews/PR-3-self-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Self-Review: PR #3 — Feature-Flag File Generation & Placeholder Tests

## What changed and why?

Added `tests/test_feature_flags.py` with 22 tests covering feature-flag behavior and metadata placeholder injection. The minimal template's feature flags (cli, tests, pypi_packaging, readme, changelog, gitignore, contributors, code_of_conduct, security) previously had no dedicated tests verifying that:
- Enabling a flag creates the expected file
- Disabling a flag omits the file
- Feature combinations work together without conflicts
- Metadata (author, email, version, description) is correctly embedded in generated files

## Why is this the right test layer (unit/integration/UI)?

These are **integration tests** — they call `generate_project()` end-to-end and inspect the filesystem output. This is the right level because:
- Feature flags affect file *generation*, not just logic — we need to verify actual files
- Mocking the filesystem would miss real bugs (e.g., directory creation order, encoding issues)
- Tests use temp directories and are fast (~0.08s total), so no need for mocking

## What could still break / what's not covered?

- Only the `minimal-python` template is tested for feature flags. Other templates (Flask, FastAPI, etc.) have their own feature sets.
- Advanced features like `mac_app_bundle`, `icon_generator`, and `github_actions` are not tested here (they're more complex and were not in scope for this issue).
- File *content* is checked for metadata presence but not for full correctness — e.g., we verify the author is in setup.py but don't assert the full file structure.

## What risks or follow-ups remain?

- Issue #5 (parameterized template tests) will cover the other templates.
- Consider adding feature-flag tests for non-minimal templates in a future issue.
- The `_apply_optional_scripts` method (mac_app_bundle, icon_generator) has significant untested code.
25 changes: 25 additions & 0 deletions docs/reviews/PR-4-self-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Self-Review: PR #4 — Parameterized Generation Tests for All Template Types

## What changed and why?

Added `tests/test_all_templates.py` with 61 tests covering all 18 registered template types. Previously only 2 templates (minimal-python and flask-web-app) had generation tests. This PR uses `pytest.mark.parametrize` to test every template, plus dedicated structure tests for 7 fully-implemented templates (Flask, FastAPI, data-science, CLI tool, binary-extension, namespace-package, plugin-framework).

## Why is this the right test layer (unit/integration/UI)?

These are **integration tests** — they call `generate_project()` with real filesystem output and verify the resulting directory structure. This is the right level because:
- Template generation involves file I/O, directory creation, and string interpolation
- Mocking the filesystem would miss real bugs (path issues, encoding, missing directories)
- Using `pytest.mark.parametrize` keeps the tests DRY and automatically covers new templates as they're added
- Tests complete in ~0.14s total, so performance is not a concern

## What could still break / what's not covered?

- **File content correctness** is not deeply verified for most templates. We check that files exist but don't assert the full content of every generated file.
- **Stub templates** (django, ml, library, game, desktop-gui, microservice, api-client, automation, jupyter-research) all fall back to `_generate_minimal_template`, so they pass but don't test unique behavior. When these stubs are implemented, the structure tests will need updating.
- **Template-specific dependencies** in generated `requirements.txt` files are not checked (e.g., Flask template should list Flask as a dependency).

## What risks or follow-ups remain?

- When stub templates get full implementations, add structure assertions for each.
- Consider adding content-level checks for requirements.txt per template.
- The `python-skeleton` template uses a download path that falls back to minimal — this is tested but the download/cache path is not exercised (would need Issue #4).
4 changes: 2 additions & 2 deletions scripts/create_icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ def create_icon():
font_size = 80
font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc",
font_size)
except:
# Fallback to default font
except Exception:
# Fallback to default font (do not catch SystemExit / KeyboardInterrupt)
font = ImageFont.load_default()

title = "PPG"
Expand Down
94 changes: 48 additions & 46 deletions src/python_project_generator/generator_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,46 +614,41 @@ def on_template_changed(self, event):

def update_template_info(self):
"""Update the template information display."""
if hasattr(self, 'template_choice') and hasattr(self, 'template_ids'):
selection = self.template_choice.GetSelection()
if selection >= 0 and selection < len(self.template_ids):
template_id = self.template_ids[selection]

# Get detailed template information
detailed_info = self.template_manager.get_template_detailed_info(template_id)

if "error" not in detailed_info:
# Update description
self.template_desc.SetLabel(detailed_info['description'])

# Update key features
features_text = "\n".join(f"• {feature}" for feature in detailed_info['key_features'])
self.template_features.SetLabel(features_text)

# Update use cases
cases_text = "\n".join(f"• {case}" for case in detailed_info['use_cases'])
self.template_cases.SetLabel(cases_text)

# Update dependencies
deps_text = ", ".join(detailed_info['dependencies'])
self.template_deps.SetLabel(deps_text)

# Update project structure
structure_text = "\n".join(detailed_info['project_structure'])
self.template_structure.SetValue(structure_text)

# Update layout
if hasattr(self, 'template_panel'):
self.template_panel.Layout()
self.template_panel.FitInside()
else:
# Handle error case
self.template_desc.SetLabel("Template information not available")
if hasattr(self, 'template_features'):
self.template_features.SetLabel("")
self.template_cases.SetLabel("")
self.template_deps.SetLabel("")
self.template_structure.SetValue("")
if not (hasattr(self, 'template_choice') and hasattr(self, 'template_ids')):
return
selection = self.template_choice.GetSelection()
if selection < 0 or selection >= len(self.template_ids):
return
template_id = self.template_ids[selection]
detailed_info = self.template_manager.get_template_detailed_info(template_id)
if "error" in detailed_info:
self._clear_template_info_display()
else:
self._apply_template_detailed_info(detailed_info)

def _clear_template_info_display(self):
"""Show unavailable template message and clear detail widgets."""
self.template_desc.SetLabel("Template information not available")
if hasattr(self, 'template_features'):
self.template_features.SetLabel("")
self.template_cases.SetLabel("")
self.template_deps.SetLabel("")
self.template_structure.SetValue("")

def _apply_template_detailed_info(self, detailed_info: Dict[str, Any]):
"""Populate template detail widgets from API result."""
self.template_desc.SetLabel(detailed_info['description'])
features_text = "\n".join(f"• {feature}" for feature in detailed_info['key_features'])
self.template_features.SetLabel(features_text)
cases_text = "\n".join(f"• {case}" for case in detailed_info['use_cases'])
self.template_cases.SetLabel(cases_text)
deps_text = ", ".join(detailed_info['dependencies'])
self.template_deps.SetLabel(deps_text)
structure_text = "\n".join(detailed_info['project_structure'])
self.template_structure.SetValue(structure_text)
if hasattr(self, 'template_panel'):
self.template_panel.Layout()
self.template_panel.FitInside()

def on_browse_output(self, event):
"""Handle browse output directory button."""
Expand Down Expand Up @@ -694,7 +689,17 @@ def on_preview(self, event):

features = self.get_selected_features()
package_name = project_name.lower().replace('-', '_').replace(' ', '_')
self._log_preview_core_structure(project_name, package_name, features)
self._log_preview_scripts_section(features)

self.log_to_output("")
self.log_to_output("=== End Preview ===")
self.log_to_output("")

def _log_preview_core_structure(
self, project_name: str, package_name: str, features: Dict[str, Any]
):
"""Emit main package and optional top-level file lines for structure preview."""
self.log_to_output(f"{project_name}/")
self.log_to_output("├── src/")
self.log_to_output(f"│ └── {package_name}/")
Expand Down Expand Up @@ -723,8 +728,9 @@ def on_preview(self, event):

if features.get('gitignore'):
self.log_to_output("├── .gitignore")

# Optional helper scripts

def _log_preview_scripts_section(self, features: Dict[str, Any]):
"""Emit optional scripts/ subtree if any helper feature is selected."""
# Optional helper scripts under scripts/
if any([
features.get('mac_app_bundle'),
Expand All @@ -744,10 +750,6 @@ def on_preview(self, event):
self.log_to_output("│ ├── freeze_requirements.py")
if features.get('setup_build_script'):
self.log_to_output("│ └── build_with_setup.py")

self.log_to_output("")
self.log_to_output("=== End Preview ===")
self.log_to_output("")

def on_generate(self, event):
"""Generate the project."""
Expand Down
Loading