Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 7 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ dependencies = []

[project.optional-dependencies]
dev = [
"pytest>=9.0",
"pytest-cov>=7.0",
"pytest-mock>=3.0",
"cookiecutter>=2.7",
"tox>=4.0",
"pyyaml>=6.0",
"ruff>=0.9",
"pytest>=9.1.1",
"pytest-cov>=7.1.0",
"pytest-mock>=3.15.1",
"cookiecutter>=2.7.1",
"tox>=4.56.1",
"pyyaml>=6.0.3",
"ruff>=0.15.20",
]

[tool.uv]
Expand Down
6 changes: 3 additions & 3 deletions test-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Test dependencies for cookiecutter template
pytest>=9.0.3
pytest>=9.1.1
cookiecutter>=2.7.1
pytest-cov>=7.1.0
pytest-mock>=3.15.1
tox>=4.53.0
pyyaml>=6.0
tox>=4.56.1
pyyaml>=6.0.3
8 changes: 2 additions & 6 deletions tests/quick_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,7 @@ def main():
python_files = []
template_files = 0

for root, dirs, files in os.walk(
os.path.join(template_dir, "{{ cookiecutter.project_slug }}")
):
for root, dirs, files in os.walk(os.path.join(template_dir, "{{ cookiecutter.project_slug }}")):
for file in files:
if file.endswith(".py"):
python_files.append(file)
Expand All @@ -109,9 +107,7 @@ def main():
if "{{ cookiecutter." in content:
template_files += 1

print(
f" ✓ Found {len(python_files)} Python files ({template_files} with template variables)"
)
print(f" ✓ Found {len(python_files)} Python files ({template_files} with template variables)")

# Summary
print("\n" + "=" * 60)
Expand Down
4 changes: 1 addition & 3 deletions tests/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ def run_basic_validation():
result = subprocess.run(
[
python_exe,
os.path.join(
os.path.dirname(os.path.dirname(__file__)), "validate_template.py"
),
os.path.join(os.path.dirname(os.path.dirname(__file__)), "validate_template.py"),
],
capture_output=True,
text=True,
Expand Down
52 changes: 13 additions & 39 deletions tests/test_cookiecutter.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,7 @@ def test_cookiecutter_json_structure(self, template_dir):
]

for field in required_fields:
assert field in config, (
f"Required field '{field}' missing from cookiecutter.json"
)
assert field in config, f"Required field '{field}' missing from cookiecutter.json"

# Check that project_slug uses proper template logic
assert "{{" in config["project_slug"], (
Expand Down Expand Up @@ -171,9 +169,7 @@ def test_accounts_module_structure(self, template_dir):

for file_path in expected_files:
full_path = os.path.join(accounts_dir, file_path)
assert os.path.exists(full_path), (
f"Expected accounts file missing: {file_path}"
)
assert os.path.exists(full_path), f"Expected accounts file missing: {file_path}"

def test_post_gen_hook_exists(self, template_dir):
"""Test that post-generation hook exists and is executable."""
Expand Down Expand Up @@ -219,9 +215,7 @@ def test_django_settings_structure(self, template_dir):
content = f.read()
# Check for Django-specific settings
if settings_file == "base.py":
assert "INSTALLED_APPS" in content, (
"base.py should contain INSTALLED_APPS"
)
assert "INSTALLED_APPS" in content, "base.py should contain INSTALLED_APPS"
assert "MIDDLEWARE" in content, "base.py should contain MIDDLEWARE"
assert "DATABASES" in content, "base.py should contain DATABASES"

Expand All @@ -246,9 +240,7 @@ def test_oauth2_package_structure(self, template_dir):

with open(file_path, "r") as f:
content = f.read()
assert content.strip(), (
f"OAuth2 file should not be empty: {oauth2_file}"
)
assert content.strip(), f"OAuth2 file should not be empty: {oauth2_file}"

# Check for specific content based on file
if oauth2_file == "providers.py":
Expand All @@ -262,9 +254,7 @@ def test_oauth2_package_structure(self, template_dir):
"providers.py should contain Facebook provider"
)
elif oauth2_file == "api.py":
assert "@router" in content, (
"api.py should contain router decorators"
)
assert "@router" in content, "api.py should contain router decorators"
assert "Router" in content, "api.py should import Router"

def test_api_package_structure(self, template_dir):
Expand All @@ -289,9 +279,7 @@ def test_api_package_structure(self, template_dir):
assert "register" in content.lower(), (
"auth.py should contain register functionality"
)
assert "login" in content.lower(), (
"auth.py should contain login functionality"
)
assert "login" in content.lower(), "auth.py should contain login functionality"
elif api_file == "users.py":
assert "router" in content.lower(), "users.py should define router"
elif api_file == "__init__.py":
Expand Down Expand Up @@ -333,40 +321,28 @@ def test_docker_configuration(self, template_dir):
# Check Dockerfile content
with open(dockerfile_path, "r") as f:
dockerfile_content = f.read()
assert "FROM python" in dockerfile_content, (
"Dockerfile should use Python base image"
)
assert "uv sync" in dockerfile_content, (
"Dockerfile should use uv sync"
)
assert "FROM python" in dockerfile_content, "Dockerfile should use Python base image"
assert "uv sync" in dockerfile_content, "Dockerfile should use uv sync"
assert "COPY pyproject.toml" in dockerfile_content, (
"Dockerfile should copy pyproject.toml"
)

# Check docker-compose content
with open(compose_path, "r") as f:
compose_content = f.read()
assert "version:" in compose_content, (
"docker-compose.yml should have version"
)
assert "services:" in compose_content, (
"docker-compose.yml should have services"
)
assert "version:" in compose_content, "docker-compose.yml should have version"
assert "services:" in compose_content, "docker-compose.yml should have services"

def test_manage_py_exists(self, template_dir):
"""Test that manage.py exists and has correct content."""
manage_path = os.path.join(
template_dir, "{{ cookiecutter.project_slug }}/manage.py"
)
manage_path = os.path.join(template_dir, "{{ cookiecutter.project_slug }}/manage.py")

assert os.path.exists(manage_path), "manage.py should exist"

with open(manage_path, "r") as f:
content = f.read()
assert "#!/usr/bin/env python" in content, "manage.py should have shebang"
assert "django.core.management" in content, (
"manage.py should import Django management"
)
assert "django.core.management" in content, "manage.py should import Django management"
assert "execute_from_command_line" in content, (
"manage.py should use execute_from_command_line"
)
Expand All @@ -378,9 +354,7 @@ def test_manage_py_exists(self, template_dir):
class TestCookiecutterGeneration:
"""Test the actual cookiecutter generation process."""

def test_cookiecutter_generation_process(
self, temp_dir, template_dir, test_context
):
def test_cookiecutter_generation_process(self, temp_dir, template_dir, test_context):
"""Test that cookiecutter can generate a project successfully."""
pytest.importorskip("cookiecutter")
from cookiecutter.main import cookiecutter
Expand Down
6 changes: 3 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,22 @@ commands =

[testenv:lint]
deps =
ruff>=0.9.0
ruff>=0.15.20
commands =
ruff check .
ruff format --check .

[testenv:format]
deps =
ruff>=0.9.0
ruff>=0.15.20
commands =
ruff check --fix .
ruff format .

[testenv:coverage]
deps =
-r{toxinidir}/test-requirements.txt
coverage>=7.0.0
coverage>=7.14.3
commands =
coverage run -m pytest tests/test_cookiecutter.py
coverage report -m
Expand Down
32 changes: 8 additions & 24 deletions validate_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,17 +166,13 @@ def test_python_files_syntax(self):
if syntax_errors:
assert False, f"Syntax errors found in non-template files: {syntax_errors}"

print(
f"✓ All {len(python_files)} Python files have valid syntax (template files skipped)"
)
print(f"✓ All {len(python_files)} Python files have valid syntax (template files skipped)")
return True

def test_requirements_files_content(self):
"""Test that requirements or pyproject.toml files have valid content."""
# Check pyproject.toml exists (primary dependency file)
project_dir = os.path.join(
self.template_dir, "{{ cookiecutter.project_slug }}"
)
project_dir = os.path.join(self.template_dir, "{{ cookiecutter.project_slug }}")
pyproject_path = os.path.join(project_dir, "pyproject.toml")
assert os.path.exists(pyproject_path), "pyproject.toml should exist"
with open(pyproject_path, "r") as f:
Expand All @@ -198,12 +194,8 @@ def test_docker_files_content(self):
assert os.path.exists(dockerfile_path), "Dockerfile should exist"
with open(dockerfile_path, "r") as f:
dockerfile_content = f.read()
assert "FROM python" in dockerfile_content, (
"Dockerfile should use Python base image"
)
assert "uv sync" in dockerfile_content, (
"Dockerfile should use uv sync"
)
assert "FROM python" in dockerfile_content, "Dockerfile should use Python base image"
assert "uv sync" in dockerfile_content, "Dockerfile should use uv sync"
assert "RUN pip install" not in dockerfile_content, (
"uv migration complete - should not use pip install"
)
Expand All @@ -212,15 +204,9 @@ def test_docker_files_content(self):
assert os.path.exists(compose_path), "docker-compose.yml should exist"
with open(compose_path, "r") as f:
compose_content = f.read()
assert "version:" in compose_content, (
"docker-compose.yml should have version"
)
assert "services:" in compose_content, (
"docker-compose.yml should have services"
)
assert "web:" in compose_content, (
"docker-compose.yml should have web service"
)
assert "version:" in compose_content, "docker-compose.yml should have version"
assert "services:" in compose_content, "docker-compose.yml should have services"
assert "web:" in compose_content, "docker-compose.yml should have web service"

print("✓ Docker files have valid content")

Expand Down Expand Up @@ -303,9 +289,7 @@ def test_comprehensive_tests_exist(self):
"OAuth2APITestCase",
]

missing_classes = [
cls for cls in required_test_classes if cls not in content
]
missing_classes = [cls for cls in required_test_classes if cls not in content]
assert not missing_classes, f"Missing test classes: {missing_classes}"

# Check for test methods
Expand Down
18 changes: 9 additions & 9 deletions {{ cookiecutter.project_slug }}/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@ dependencies = [
"psycopg[binary]>=3.2,<4.0",
"python-decouple>=3.8,<4.0",
"dj-database-url>=3.1,<4.0",
"gunicorn>=25.3,<26.0",
"gunicorn>=26.0,<27.0",
"django-guardian>=3.3,<4.0",
"djangorestframework-simplejwt>=5.5,<6.0",
"django-redis>=6.0,<7.0",
"django-redis>=7.0,<8.0",
"whitenoise[brotli]>=6.12,<7.0",
{% if cookiecutter.use_celery == 'y' %}
"celery>=5.6,<6.0",
"redis>=7.4,<8.0",
"redis>=8.0,<9.0",
"django-celery-beat>=2.9,<3.0",
"django-celery-results>=2.6,<3.0",
{% endif %}
{% if cookiecutter.include_oauth2 == 'y' %}
"django-oauth-toolkit>=3.2,<4.0",
"requests-oauthlib>=2.0,<3.0",
"social-auth-app-django>=5.7,<6.0",
"social-auth-app-django>=6.0,<7.0",
{% endif %}
{% if cookiecutter.include_sentry == 'y' %}
"sentry-sdk[django]>=2.58,<3.0",
Expand All @@ -33,14 +33,14 @@ dependencies = [
[project.optional-dependencies]
dev = [
"django-extensions>=4.1,<5.0",
"django-debug-toolbar>=6.3,<7.0",
"django-debug-toolbar>=7.0,<8.0",
"Werkzeug>=3.1,<4.0",
"pytest>=9.0,<10.0",
"pytest>=9.1.1,<10.0",
"pytest-django>=4.12,<5.0",
"pytest-cov>=7.1,<8.0",
"ruff>=0.9,<1.0",
"pre-commit>=4.0,<5.0",
"coverage>=7.0,<8.0",
"ruff>=0.15.20,<1.0",
"pre-commit>=4.6,<5.0",
"coverage>=7.14.3,<8.0",
]
prod = [
"whitenoise[brotli]>=6.12,<7.0",
Expand Down
Loading