diff --git a/.gitea/workflows/test-templates.yml b/.gitea/workflows/test-templates.yml
new file mode 100644
index 0000000..e35d2ec
--- /dev/null
+++ b/.gitea/workflows/test-templates.yml
@@ -0,0 +1,460 @@
+name: Test CI Templates
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ schedule:
+ - cron: '0 2 * * *' # Run daily at 2 AM
+ workflow_dispatch: # Manual trigger
+
+jobs:
+ validate-syntax:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+
+ - name: Install yamllint
+ run: pip install yamllint
+
+ - name: Validate YAML syntax
+ run: |
+ echo "Validating workflow YAML syntax..."
+ find .gitea/workflows -name "*.yml" -exec yamllint {} \;
+
+ - name: Check workflow structure
+ run: |
+ echo "Checking workflow structure..."
+ for file in .gitea/workflows/**/*.yml; do
+ echo "Checking $file..."
+ if ! grep -q "workflow_call" "$file"; then
+ echo "Error: $file is missing workflow_call trigger"
+ exit 1
+ fi
+ if ! grep -q "inputs:" "$file"; then
+ echo "Warning: $file has no inputs defined"
+ fi
+ done
+
+ test-python-code-quality:
+ runs-on: ubuntu-latest
+ needs: validate-syntax
+ strategy:
+ matrix:
+ python-version: ["3.10", "3.11", "3.12"]
+ include:
+ - test-type: "basic"
+ skip-mypy: false
+ skip-pyupgrade: false
+ - test-type: "skip-mypy"
+ skip-mypy: true
+ skip-pyupgrade: false
+ - test-type: "skip-pyupgrade"
+ skip-mypy: false
+ skip-pyupgrade: true
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Create test Python project
+ run: |
+ mkdir -p test-project/src/test_package
+ cd test-project
+
+ # Create pyproject.toml
+ cat > pyproject.toml << 'EOF'
+ [build-system]
+ requires = ["hatchling"]
+ build-backend = "hatchling.build"
+
+ [project]
+ name = "test-package"
+ version = "0.1.0"
+ description = "Test package for CI templates"
+ dependencies = []
+
+ [project.optional-dependencies]
+ dev = [
+ "mypy>=1.0.0",
+ "ruff>=0.1.0",
+ "pyupgrade>=3.0.0",
+ ]
+
+ [tool.ruff]
+ line-length = 88
+ target-version = "py310"
+
+ [tool.mypy]
+ python_version = "3.10"
+ warn_return_any = true
+ warn_unused_configs = true
+ EOF
+
+ # Create source code
+ cat > src/test_package/__init__.py << 'EOF'
+ """Test package for CI template validation."""
+ __version__ = "0.1.0"
+
+ def hello_world() -> str:
+ """Return a greeting message."""
+ return "Hello, World!"
+ EOF
+
+ cat > src/test_package/main.py << 'EOF'
+ """Main module for test package."""
+ from typing import List
+
+ def process_items(items: List[str]) -> List[str]:
+ """Process a list of items."""
+ return [item.upper() for item in items]
+
+ if __name__ == "__main__":
+ test_items = ["hello", "world"]
+ result = process_items(test_items)
+ print(result)
+ EOF
+
+ - name: Test Python code quality workflow
+ uses: ./
+ with:
+ python-version: ${{ matrix.python-version }}
+ working-directory: "./test-project"
+ skip-mypy: ${{ matrix.skip-mypy }}
+ skip-pyupgrade: ${{ matrix.skip-pyupgrade }}
+ uv-extras: "dev"
+
+ test-python-pytest:
+ runs-on: ubuntu-latest
+ needs: validate-syntax
+ strategy:
+ matrix:
+ python-version: ["3.11", "3.12"]
+ coverage: [true, false]
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Create test Python project with tests
+ run: |
+ mkdir -p test-pytest-project/src/test_package tests
+ cd test-pytest-project
+
+ # Create pyproject.toml
+ cat > pyproject.toml << 'EOF'
+ [build-system]
+ requires = ["hatchling"]
+ build-backend = "hatchling.build"
+
+ [project]
+ name = "test-package"
+ version = "0.1.0"
+ description = "Test package for pytest CI template"
+ dependencies = []
+
+ [project.optional-dependencies]
+ test = [
+ "pytest>=7.0.0",
+ "pytest-cov>=4.0.0",
+ ]
+
+ [tool.pytest.ini_options]
+ testpaths = ["tests"]
+ python_files = ["test_*.py"]
+ addopts = ["--strict-markers", "--strict-config"]
+ EOF
+
+ # Create source code
+ cat > src/test_package/__init__.py << 'EOF'
+ """Test package for pytest template validation."""
+ __version__ = "0.1.0"
+ EOF
+
+ cat > src/test_package/calculator.py << 'EOF'
+ """Simple calculator for testing."""
+
+ def add(a: float, b: float) -> float:
+ """Add two numbers."""
+ return a + b
+
+ def multiply(a: float, b: float) -> float:
+ """Multiply two numbers."""
+ return a * b
+
+ def divide(a: float, b: float) -> float:
+ """Divide two numbers."""
+ if b == 0:
+ raise ValueError("Cannot divide by zero")
+ return a / b
+ EOF
+
+ # Create tests
+ cat > tests/test_calculator.py << 'EOF'
+ """Tests for calculator module."""
+ import pytest
+ from test_package.calculator import add, multiply, divide
+
+ def test_add():
+ assert add(2, 3) == 5
+ assert add(-1, 1) == 0
+ assert add(0.1, 0.2) == pytest.approx(0.3)
+
+ def test_multiply():
+ assert multiply(2, 3) == 6
+ assert multiply(-2, 3) == -6
+ assert multiply(0, 5) == 0
+
+ def test_divide():
+ assert divide(6, 2) == 3
+ assert divide(5, 2) == 2.5
+
+ def test_divide_by_zero():
+ with pytest.raises(ValueError, match="Cannot divide by zero"):
+ divide(5, 0)
+ EOF
+
+ - name: Test pytest workflow
+ uses: ./
+ with:
+ python-version: ${{ matrix.python-version }}
+ working-directory: "./test-pytest-project"
+ coverage-package: ${{ matrix.coverage && 'test_package' || '' }}
+ pytest-args: "--verbose"
+ uv-extras: "test"
+
+ test-web-formatting:
+ runs-on: ubuntu-latest
+ needs: validate-syntax
+ strategy:
+ matrix:
+ node-version: ["18", "20"]
+ check-yaml: [true, false]
+ check-json: [true, false]
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Create test web project
+ run: |
+ mkdir -p test-web-project/src/components
+ cd test-web-project
+
+ # Create package.json
+ cat > package.json << 'EOF'
+ {
+ "name": "test-web-project",
+ "version": "1.0.0",
+ "description": "Test project for web formatting template"
+ }
+ EOF
+
+ # Create JavaScript files
+ cat > src/index.js << 'EOF'
+ // Test JavaScript file for formatting
+ const greeting = "Hello, World!";
+
+ function greet(name) {
+ return `Hello, ${name}!`;
+ }
+
+ export { greet };
+ EOF
+
+ cat > src/components/Button.js << 'EOF'
+ // Button component
+ export default function Button({ children, onClick }) {
+ return (
+
+ );
+ }
+ EOF
+
+ # Create JSON file
+ cat > config.json << 'EOF'
+ {
+ "name": "test-config",
+ "version": "1.0.0",
+ "settings": {
+ "debug": true,
+ "timeout": 5000
+ }
+ }
+ EOF
+
+ # Create YAML file
+ cat > config.yml << 'EOF'
+ name: test-config
+ version: 1.0.0
+ settings:
+ debug: true
+ timeout: 5000
+ features:
+ - feature1
+ - feature2
+ EOF
+
+ # Create Prettier config
+ cat > .prettierrc.json << 'EOF'
+ {
+ "semi": true,
+ "trailingComma": "es5",
+ "singleQuote": true,
+ "printWidth": 80,
+ "tabWidth": 2
+ }
+ EOF
+
+ - name: Test web formatting workflow
+ uses: ./
+ with:
+ node-version: ${{ matrix.node-version }}
+ working-directory: "./test-web-project"
+ check-yaml: ${{ matrix.check-yaml }}
+ check-json: ${{ matrix.check-json }}
+
+ test-monorepo:
+ runs-on: ubuntu-latest
+ needs: validate-syntax
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Create monorepo structure
+ run: |
+ mkdir -p monorepo-test/{backend,frontend}
+
+ # Backend Python project
+ cd monorepo-test/backend
+ cat > pyproject.toml << 'EOF'
+ [build-system]
+ requires = ["hatchling"]
+ build-backend = "hatchling.build"
+
+ [project]
+ name = "backend-service"
+ version = "0.1.0"
+ dependencies = []
+
+ [project.optional-dependencies]
+ dev = ["ruff>=0.1.0", "mypy>=1.0.0"]
+ EOF
+
+ mkdir -p src/backend_service
+ cat > src/backend_service/__init__.py << 'EOF'
+ """Backend service package."""
+ __version__ = "0.1.0"
+ EOF
+
+ # Frontend project
+ cd ../frontend
+ cat > package.json << 'EOF'
+ {
+ "name": "frontend-app",
+ "version": "1.0.0"
+ }
+ EOF
+
+ mkdir -p src
+ cat > src/App.js << 'EOF'
+ export default function App() {
+ return
Hello from frontend!
;
+ }
+ EOF
+
+ - name: Test backend code quality
+ uses: ./
+ with:
+ working-directory: "./monorepo-test/backend"
+ uv-extras: "dev"
+
+ - name: Test frontend formatting
+ uses: ./
+ with:
+ working-directory: "./monorepo-test/frontend"
+
+ test-edge-cases:
+ runs-on: ubuntu-latest
+ needs: validate-syntax
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Test minimal Python project
+ run: |
+ mkdir minimal-python
+ cd minimal-python
+ cat > pyproject.toml << 'EOF'
+ [build-system]
+ requires = ["hatchling"]
+ build-backend = "hatchling.build"
+
+ [project]
+ name = "minimal"
+ version = "0.1.0"
+ dependencies = []
+ EOF
+
+ mkdir -p src/minimal
+ cat > src/minimal/__init__.py << 'EOF'
+ pass
+ EOF
+
+ - name: Test with minimal configuration
+ uses: ./
+ with:
+ working-directory: "./minimal-python"
+ skip-mypy: true
+ skip-pyupgrade: true
+
+ test-results:
+ runs-on: ubuntu-latest
+ needs: [test-python-code-quality, test-python-pytest, test-web-formatting, test-monorepo, test-edge-cases]
+ if: always()
+
+ steps:
+ - name: Report test results
+ run: |
+ echo "## CI Template Test Results" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ if [ "${{ needs.test-python-code-quality.result }}" == "success" ]; then
+ echo "✅ Python Code Quality: PASSED" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "❌ Python Code Quality: FAILED" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ if [ "${{ needs.test-python-pytest.result }}" == "success" ]; then
+ echo "✅ Python Pytest: PASSED" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "❌ Python Pytest: FAILED" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ if [ "${{ needs.test-web-formatting.result }}" == "success" ]; then
+ echo "✅ Web Formatting: PASSED" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "❌ Web Formatting: FAILED" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ if [ "${{ needs.test-monorepo.result }}" == "success" ]; then
+ echo "✅ Monorepo Support: PASSED" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "❌ Monorepo Support: FAILED" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ if [ "${{ needs.test-edge-cases.result }}" == "success" ]; then
+ echo "✅ Edge Cases: PASSED" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "❌ Edge Cases: FAILED" >> $GITHUB_STEP_SUMMARY
+ fi
\ No newline at end of file
diff --git a/TESTING.md b/TESTING.md
new file mode 100644
index 0000000..a8428ad
--- /dev/null
+++ b/TESTING.md
@@ -0,0 +1,389 @@
+# CI Template Testing Guide
+
+This document explains how to test and validate the CI workflow templates in this repository.
+
+## Overview
+
+The testing infrastructure includes:
+
+- **Automated validation workflow** (`.gitea/workflows/validate-templates.yml`)
+- **Manual validation script** (`scripts/validate-templates.sh`)
+- **Test projects** (`test-projects/`)
+- **Matrix testing** across multiple configurations
+
+## Quick Start
+
+### 1. Run Local Validation
+
+```bash
+# Make the script executable
+chmod +x scripts/validate-templates.sh
+
+# Validate all workflows
+./scripts/validate-templates.sh
+
+# List available templates
+./scripts/validate-templates.sh list
+
+# Show help
+./scripts/validate-templates.sh help
+```
+
+### 2. Test with Sample Projects
+
+```bash
+# Navigate to a test project
+cd test-projects/python-basic
+
+# Install dependencies (if using UV)
+uv sync --all-extras
+
+# Run local tests
+uv run pytest
+uv run mypy .
+uv run ruff check .
+```
+
+## Testing Infrastructure
+
+### 1. Validation Workflow (`.gitea/workflows/validate-templates.yml`)
+
+Automatically runs on every push and pull request:
+
+- **Matrix testing** across Python versions (3.10, 3.11, 3.12)
+- **Template validation** for all workflow files
+- **Integration testing** with sample projects
+- **Documentation checks**
+
+**Triggered by:**
+
+- Push to `main` branch
+- Pull requests
+- Manual dispatch
+- Daily schedule (2 AM UTC)
+
+**Test matrix:**
+
+```yaml
+strategy:
+ matrix:
+ python-version: ["3.10", "3.11", "3.12"]
+ template: ["code-quality", "pytest"]
+ include:
+ - template: "formatting"
+ python-version: "3.12" # Only test once for web template
+```
+
+### 2. Validation Script (`scripts/validate-templates.sh`)
+
+**Features:**
+
+- YAML syntax validation with `yamllint`
+- Workflow structure validation with `yq`
+- Reusable workflow detection
+- Input parameter validation
+- Documentation checks
+- Common issue detection
+
+**Usage:**
+
+```bash
+# Basic validation
+./scripts/validate-templates.sh
+
+# List all templates
+./scripts/validate-templates.sh list
+
+# Help
+./scripts/validate-templates.sh help
+```
+
+**Dependencies:**
+
+- `yamllint` (optional): `pip install yamllint`
+- `yq` (optional): `brew install yq` (macOS) or `apt install yq` (Ubuntu)
+
+**Note:** The script works without dependencies but provides more detailed validation when they're available.
+
+### 3. Test Projects
+
+#### Python Basic (`test-projects/python-basic/`)
+
+**Purpose:** Test Python-specific templates
+
+**Structure:**
+
+```text
+python-basic/
+├── .gitea/workflows/
+│ └── test-templates.yml # Tests both code-quality and pytest templates
+├── src/test_package/
+│ ├── __init__.py
+│ ├── calculator.py # Sample module with type hints
+│ └── utils.py # Utility functions
+├── tests/
+│ ├── test_calculator.py # Pytest tests
+│ └── test_utils.py # More tests
+├── pyproject.toml # UV-compatible project config
+└── README.md
+```
+
+**Features:**
+
+- Type hints for mypy testing
+- Pytest with coverage
+- Ruff-compatible code style
+- UV package management
+
+#### Web Basic (`test-projects/web-basic/`)
+
+**Purpose:** Test web formatting template
+
+**Structure:**
+
+```text
+web-basic/
+├── .gitea/workflows/
+│ └── test-formatting.yml # Tests formatting template
+├── package.json # Node.js project
+├── index.js # JavaScript file
+├── config.yml # YAML file for validation
+├── .prettierrc.json # Prettier configuration
+└── README.md
+```
+
+**Features:**
+
+- Prettier configuration
+- YAML validation
+- JSON validation
+- Node.js setup
+
+## Testing Workflow Templates
+
+### 1. Local Testing with Act
+
+Install and use Act to test workflows locally:
+
+```bash
+# Install Act
+brew install act # macOS
+# or
+curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
+
+# Test a specific workflow
+act -W .gitea/workflows/python/code-quality.yml
+
+# Test with inputs
+act workflow_call -W .gitea/workflows/python/code-quality.yml \
+ --input python-version=3.11 \
+ --input working-directory=./test-projects/python-basic
+```
+
+### 2. Branch Testing
+
+Test changes on feature branches:
+
+```bash
+# Create feature branch
+git checkout -b feature/improve-python-template
+
+# Make changes to templates
+# ...
+
+# Update test project to use feature branch
+# In test-projects/python-basic/.gitea/workflows/test-templates.yml:
+# uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/code-quality.yml@feature/improve-python-template
+
+# Push and test
+git push origin feature/improve-python-template
+```
+
+### 3. Matrix Testing
+
+Test multiple configurations:
+
+```yaml
+# Example matrix test
+strategy:
+ matrix:
+ python-version: ["3.10", "3.11", "3.12"]
+ os: ["ubuntu-latest", "ubuntu-20.04"]
+ working-directory: [".", "./src"]
+```
+
+## Validation Checklist
+
+### Workflow File Validation
+
+- [ ] Valid YAML syntax
+- [ ] Contains required fields: `name`, `on`, `jobs`
+- [ ] Uses `workflow_call` trigger for reusable workflows
+- [ ] All inputs have descriptions and types
+- [ ] No hardcoded values that should be inputs
+- [ ] Proper defaults for optional inputs
+
+### Template Testing
+
+- [ ] All input combinations work correctly
+- [ ] Error handling for invalid inputs
+- [ ] Backwards compatibility maintained
+- [ ] Documentation updated
+- [ ] Test projects updated
+
+### Integration Testing
+
+- [ ] Works with real projects
+- [ ] Performance is acceptable
+- [ ] No conflicts between templates
+- [ ] Secrets handling works correctly
+
+## Common Issues and Solutions
+
+### 1. YAML Syntax Errors
+
+**Problem:** Invalid YAML syntax
+
+```bash
+Error: YAML syntax validation failed
+```
+
+**Solution:**
+
+- Use `yamllint` to check syntax
+- Validate indentation (use spaces, not tabs)
+- Check for missing quotes around strings with special characters
+
+### 2. Missing Required Fields
+
+**Problem:** Workflow missing required fields
+
+```bash
+Error: Missing required field 'on' in workflow.yml
+```
+
+**Solution:**
+
+- Ensure all workflows have `name`, `on`, and `jobs` fields
+- For reusable workflows, use `workflow_call` trigger
+
+### 3. Input Validation Issues
+
+**Problem:** Missing input descriptions or types
+
+```bash
+Warning: Input 'python-version' missing description
+```
+
+**Solution:**
+
+```yaml
+inputs:
+ python-version:
+ description: 'Python version to use (e.g., "3.12")'
+ required: false
+ type: string
+ default: "3.12"
+```
+
+### 4. Test Project Issues
+
+**Problem:** Tests fail in sample projects
+
+```bash
+Error: Module not found
+```
+
+**Solutions:**
+
+- Check `pyproject.toml` configuration
+- Verify package structure
+- Ensure dependencies are correctly specified
+- Check import paths
+
+## CI/CD Best Practices
+
+### 1. Version Control
+
+- Use semantic versioning for releases
+- Tag stable versions: `v1.0.0`, `v1.1.0`, etc.
+- Reference specific versions in production: `@v1.0.0`
+- Use `@main` for development/testing
+
+### 2. Backwards Compatibility
+
+- Test with previous versions
+- Provide migration guides for breaking changes
+- Deprecate features gracefully
+- Maintain changelog
+
+### 3. Security
+
+- Validate all inputs
+- Use secrets for sensitive data
+- Limit permissions to minimum required
+- Regular security updates
+
+### 4. Documentation
+
+- Keep README files updated
+- Document all input parameters
+- Provide usage examples
+- Include troubleshooting guides
+
+## Troubleshooting
+
+### Validation Script Issues
+
+```bash
+# Check dependencies
+./scripts/validate-templates.sh
+
+# Manual YAML validation
+yamllint .gitea/workflows/python/code-quality.yml
+
+# Manual structure check
+yq eval '.on | has("workflow_call")' .gitea/workflows/python/code-quality.yml
+```
+
+### Test Project Issues
+
+```bash
+# Python projects
+cd test-projects/python-basic
+uv sync --all-extras
+uv run pytest --verbose
+
+# Web projects
+cd test-projects/web-basic
+npm install
+npm run format:check
+```
+
+### Workflow Issues
+
+```bash
+# Check workflow logs in Gitea
+# Enable debug logging:
+env:
+ ACTIONS_STEP_DEBUG: true
+ ACTIONS_RUNNER_DEBUG: true
+```
+
+## Contributing
+
+When adding new templates or making changes:
+
+1. **Create/update test projects** to validate the changes
+2. **Run validation script** locally before committing
+3. **Update documentation** as needed
+4. **Test with feature branches** before merging
+5. **Tag releases** for stable versions
+
+## Resources
+
+- [Gitea Actions Documentation](https://docs.gitea.io/en-us/actions/)
+- [GitHub Actions Syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions)
+- [Act - Local Testing Tool](https://github.com/nektos/act)
+- [yamllint Documentation](https://yamllint.readthedocs.io/)
+- [yq Documentation](https://mikefarah.gitbook.io/yq/)
\ No newline at end of file
diff --git a/scripts/validate-templates.sh b/scripts/validate-templates.sh
new file mode 100755
index 0000000..fab21b7
--- /dev/null
+++ b/scripts/validate-templates.sh
@@ -0,0 +1,709 @@
+#!/bin/bash
+set -e
+
+# CI Template Validation Script
+# This script validates the syntax and structure of Gitea Actions workflow templates
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# Configuration
+WORKFLOW_DIR=".gitea/workflows"
+REQUIRED_FIELDS=("name" "on" "jobs")
+REUSABLE_TRIGGER="workflow_call"
+
+# Counters
+TOTAL_FILES=0
+VALID_FILES=0
+WARNINGS=0
+
+# Helper functions
+log_info() {
+ echo -e "${BLUE}[INFO]${NC} $1"
+}
+
+log_success() {
+ echo -e "${GREEN}[SUCCESS]${NC} $1"
+}
+
+log_warning() {
+ echo -e "${YELLOW}[WARNING]${NC} $1"
+ ((WARNINGS++))
+}
+
+log_error() {
+ echo -e "${RED}[ERROR]${NC} $1"
+}
+
+# Check if required tools are installed
+check_dependencies() {
+ log_info "Checking dependencies..."
+
+ if ! command -v yamllint &> /dev/null; then
+ log_warning "yamllint not found. Install with: pip install yamllint"
+ log_info "Will skip YAML syntax validation"
+ SKIP_YAMLLINT=true
+ else
+ log_success "yamllint found"
+ fi
+
+ if ! command -v yq &> /dev/null; then
+ log_warning "yq not found. Install with: brew install yq (macOS) or apt install yq (Ubuntu)"
+ log_info "Will use basic grep validation instead"
+ SKIP_YQ=true
+ else
+ log_success "yq found"
+ fi
+}
+
+# Validate YAML syntax
+validate_yaml_syntax() {
+ local file="$1"
+
+ if [[ "$SKIP_YAMLLINT" == "true" ]]; then
+ return 0
+ fi
+
+ if yamllint "$file" 2>/dev/null; then
+ return 0
+ else
+ log_error "YAML syntax validation failed for $file"
+ return 1
+ fi
+}
+
+# Check if file is a reusable workflow
+is_reusable_workflow() {
+ local file="$1"
+
+ if [[ "$SKIP_YQ" == "true" ]]; then
+ # Fallback to grep
+ if grep -q "workflow_call" "$file"; then
+ return 0
+ fi
+ else
+ # Use yq for precise checking
+ if yq eval '.on | has("workflow_call")' "$file" 2>/dev/null | grep -q "true"; then
+ return 0
+ fi
+ fi
+
+ return 1
+}
+
+# Validate required workflow fields
+validate_workflow_structure() {
+ local file="$1"
+ local valid=true
+
+ for field in "${REQUIRED_FIELDS[@]}"; do
+ if [[ "$SKIP_YQ" == "true" ]]; then
+ # Fallback to grep
+ if ! grep -q "^${field}:" "$file"; then
+ log_error "Missing required field '$field' in $file"
+ valid=false
+ fi
+ else
+ # Use yq for precise checking
+ if ! yq eval "has(\"$field\")" "$file" 2>/dev/null | grep -q "true"; then
+ log_error "Missing required field '$field' in $file"
+ valid=false
+ fi
+ fi
+ done
+
+ if [[ "$valid" == "true" ]]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+# Validate workflow inputs (for reusable workflows)
+validate_workflow_inputs() {
+ local file="$1"
+
+ if ! is_reusable_workflow "$file"; then
+ return 0
+ fi
+
+ if [[ "$SKIP_YQ" == "true" ]]; then
+ # Basic check with grep
+ if grep -q "inputs:" "$file"; then
+ log_success "Inputs section found in $file"
+ else
+ log_warning "No inputs section found in reusable workflow $file"
+ fi
+ else
+ # Detailed validation with yq
+ local inputs_count
+ inputs_count=$(yq eval '.on.workflow_call.inputs | length' "$file" 2>/dev/null || echo "0")
+
+ if [[ "$inputs_count" -gt 0 ]]; then
+ log_success "Found $inputs_count input(s) in $file"
+
+ # Validate each input has required fields
+ local input_names
+ input_names=$(yq eval '.on.workflow_call.inputs | keys | .[]' "$file" 2>/dev/null || echo "")
+
+ while IFS= read -r input_name; do
+ if [[ -n "$input_name" ]]; then
+ # Check for description
+ if ! yq eval ".on.workflow_call.inputs.${input_name} | has(\"description\")" "$file" 2>/dev/null | grep -q "true"; then
+ log_warning "Input '$input_name' missing description in $file"
+ fi
+
+ # Check for type
+ if ! yq eval ".on.workflow_call.inputs.${input_name} | has(\"type\")" "$file" 2>/dev/null | grep -q "true"; then
+ log_warning "Input '$input_name' missing type in $file"
+ fi
+ fi
+ done <<< "$input_names"
+ else
+ log_warning "No inputs found in reusable workflow $file"
+ fi
+ fi
+}
+
+# Validate a single workflow file
+validate_workflow_file() {
+ local file="$1"
+ local filename
+ filename=$(basename "$file")
+
+ log_info "Validating $file..."
+ ((TOTAL_FILES++))
+
+ # Check if file exists and is readable
+ if [[ ! -r "$file" ]]; then
+ log_error "Cannot read file: $file"
+ return 1
+ fi
+
+ # Validate YAML syntax
+ if ! validate_yaml_syntax "$file"; then
+ return 1
+ fi
+
+ # Validate workflow structure
+ if ! validate_workflow_structure "$file"; then
+ return 1
+ fi
+
+ # Additional validations for reusable workflows
+ if is_reusable_workflow "$file"; then
+ log_success "$filename is a reusable workflow"
+ validate_workflow_inputs "$file"
+ else
+ log_warning "$filename is not a reusable workflow (missing workflow_call trigger)"
+ fi
+
+ # Check for common issues
+ if grep -q "uses: \./\|uses: \.\\\\" "$file"; then
+ log_warning "Found relative path in 'uses' field in $file - this may cause issues"
+ fi
+
+ # Check for hardcoded values that should be inputs
+ if grep -q "python-version: [\"']3\." "$file"; then
+ log_warning "Found hardcoded Python version in $file - consider making it an input"
+ fi
+
+ ((VALID_FILES++))
+ log_success "$filename validation completed"
+ return 0
+}
+
+# Validate all workflow files
+validate_all_workflows() {
+ log_info "Starting workflow validation..."
+
+ if [[ ! -d "$WORKFLOW_DIR" ]]; then
+ log_error "Workflow directory '$WORKFLOW_DIR' not found"
+ exit 1
+ fi
+
+ # Find all YAML files in workflow directory
+ local workflow_files
+ workflow_files=$(find "$WORKFLOW_DIR" -name "*.yml" -o -name "*.yaml" 2>/dev/null)
+
+ if [[ -z "$workflow_files" ]]; then
+ log_error "No workflow files found in '$WORKFLOW_DIR'"
+ exit 1
+ fi
+
+ # Validate each file
+ local failed_files=0
+ while IFS= read -r file; do
+ if [[ -n "$file" ]]; then
+ if ! validate_workflow_file "$file"; then
+ ((failed_files++))
+ fi
+ fi
+ done <<< "$workflow_files"
+
+ # Print summary
+ echo
+ log_info "=== Validation Summary ==="
+ log_info "Total files: $TOTAL_FILES"
+ log_success "Valid files: $VALID_FILES"
+
+ if [[ $WARNINGS -gt 0 ]]; then
+ log_warning "Warnings: $WARNINGS"
+ fi
+
+ if [[ $failed_files -gt 0 ]]; then
+ log_error "Failed files: $failed_files"
+ exit 1
+ fi
+
+ if [[ $WARNINGS -gt 0 ]]; then
+ log_warning "Validation completed with warnings"
+ exit 0
+ fi
+
+ log_success "All workflows validated successfully!"
+}
+
+# Validate documentation files
+validate_documentation() {
+ log_info "Validating documentation..."
+
+ local readme_files=("README.md" "README-python-code-quality.md" "README-python-pytest.md" "README-web-formatting.md")
+
+ for readme in "${readme_files[@]}"; do
+ if [[ -f "$readme" ]]; then
+ log_success "Found $readme"
+
+ # Check for basic sections
+ if ! grep -q "^# " "$readme"; then
+ log_warning "$readme missing main heading"
+ fi
+
+ if ! grep -q "## Usage" "$readme"; then
+ log_warning "$readme missing Usage section"
+ fi
+
+ else
+ log_warning "Missing documentation file: $readme"
+ fi
+ done
+}
+
+# Generate workflow file list
+list_workflows() {
+ log_info "Available workflow templates:"
+ echo
+
+ if [[ -d "$WORKFLOW_DIR" ]]; then
+ find "$WORKFLOW_DIR" -name "*.yml" -o -name "*.yaml" | while read -r file; do
+ local relative_path
+ relative_path=$(echo "$file" | sed "s|^$WORKFLOW_DIR/||")
+
+ if is_reusable_workflow "$file"; then
+ echo -e " ${GREEN}✓${NC} $relative_path (reusable)"
+ else
+ echo -e " ${YELLOW}○${NC} $relative_path (not reusable)"
+ fi
+ done
+ else
+ log_error "Workflow directory not found"
+ fi
+ echo
+}
+
+# Main function
+main() {
+ echo -e "${BLUE}CI Template Validator${NC}"
+ echo "======================"
+ echo
+
+ # Parse command line arguments
+ case "${1:-validate}" in
+ "validate")
+ check_dependencies
+ validate_all_workflows
+ validate_documentation
+ ;;
+ "list")
+ list_workflows
+ ;;
+ "help"|"-h"|"--help")
+ echo "Usage: $0 [command]"
+ echo
+ echo "Commands:"
+ echo " validate Validate all workflow files (default)"
+ echo " list List all workflow templates"
+ echo " help Show this help message"
+ echo
+ echo "Examples:"
+ echo " $0 # Validate all workflows"
+ echo " $0 validate # Same as above"
+ echo " $0 list # List all available templates"
+ ;;
+ *)
+ log_error "Unknown command: $1"
+ echo "Use '$0 help' for usage information"
+ exit 1
+ ;;
+ esac
+}
+
+# Run main function with all arguments
+main "$@"
+
+echo -e "${BLUE}🔍 CI Template Validation Script${NC}"
+echo "=================================="
+
+# Function to print status
+print_status() {
+ if [ $1 -eq 0 ]; then
+ echo -e "${GREEN}✅ $2${NC}"
+ else
+ echo -e "${RED}❌ $2${NC}"
+ return 1
+ fi
+}
+
+# Function to print warning
+print_warning() {
+ echo -e "${YELLOW}⚠️ $1${NC}"
+}
+
+# Function to print info
+print_info() {
+ echo -e "${BLUE}ℹ️ $1${NC}"
+}
+
+# Check if required tools are installed
+check_dependencies() {
+ echo -e "\n${BLUE}Checking dependencies...${NC}"
+
+ local missing_deps=()
+
+ if ! command -v yamllint &> /dev/null; then
+ missing_deps+=("yamllint")
+ fi
+
+ if ! command -v python3 &> /dev/null; then
+ missing_deps+=("python3")
+ fi
+
+ if [ ${#missing_deps[@]} -gt 0 ]; then
+ echo -e "${RED}Missing dependencies: ${missing_deps[*]}${NC}"
+ echo "Install with:"
+ for dep in "${missing_deps[@]}"; do
+ case $dep in
+ yamllint)
+ echo " pip install yamllint"
+ ;;
+ python3)
+ echo " Install Python 3.x"
+ ;;
+ esac
+ done
+ return 1
+ fi
+
+ print_status 0 "All dependencies available"
+}
+
+# Validate YAML syntax
+validate_yaml_syntax() {
+ echo -e "\n${BLUE}Validating YAML syntax...${NC}"
+
+ local yaml_files=()
+ while IFS= read -r -d $'\0'; do
+ yaml_files+=("$REPLY")
+ done < <(find .gitea/workflows -name "*.yml" -print0 2>/dev/null)
+
+ if [ ${#yaml_files[@]} -eq 0 ]; then
+ print_warning "No YAML files found in .gitea/workflows/"
+ return 1
+ fi
+
+ local errors=0
+ for file in "${yaml_files[@]}"; do
+ if yamllint "$file" > /dev/null 2>&1; then
+ print_status 0 "YAML syntax valid: $file"
+ else
+ print_status 1 "YAML syntax error: $file"
+ yamllint "$file"
+ ((errors++))
+ fi
+ done
+
+ if [ $errors -eq 0 ]; then
+ print_status 0 "All YAML files have valid syntax"
+ else
+ print_status 1 "$errors YAML files have syntax errors"
+ return 1
+ fi
+}
+
+# Validate workflow structure
+validate_workflow_structure() {
+ echo -e "\n${BLUE}Validating workflow structure...${NC}"
+
+ local workflow_files=()
+ while IFS= read -r -d $'\0'; do
+ workflow_files+=("$REPLY")
+ done < <(find .gitea/workflows -name "*.yml" -print0 2>/dev/null)
+
+ local errors=0
+ for file in "${workflow_files[@]}"; do
+ echo -e "\nChecking $file..."
+
+ # Check for workflow_call trigger
+ if grep -q "workflow_call" "$file"; then
+ print_status 0 "Has workflow_call trigger"
+ else
+ print_status 1 "Missing workflow_call trigger"
+ ((errors++))
+ fi
+
+ # Check for inputs section
+ if grep -q "inputs:" "$file"; then
+ print_status 0 "Has inputs section"
+ else
+ print_warning "No inputs section (may be intentional)"
+ fi
+
+ # Check for jobs section
+ if grep -q "jobs:" "$file"; then
+ print_status 0 "Has jobs section"
+ else
+ print_status 1 "Missing jobs section"
+ ((errors++))
+ fi
+
+ # Check for runs-on
+ if grep -q "runs-on:" "$file"; then
+ print_status 0 "Has runs-on specified"
+ else
+ print_status 1 "Missing runs-on specification"
+ ((errors++))
+ fi
+
+ # Check for checkout action
+ if grep -q "actions/checkout" "$file"; then
+ print_status 0 "Uses checkout action"
+ else
+ print_warning "No checkout action (may be intentional)"
+ fi
+ done
+
+ if [ $errors -eq 0 ]; then
+ print_status 0 "All workflows have valid structure"
+ else
+ print_status 1 "$errors workflows have structure issues"
+ return 1
+ fi
+}
+
+# Validate input parameters
+validate_input_parameters() {
+ echo -e "\n${BLUE}Validating input parameters...${NC}"
+
+ local workflow_files=()
+ while IFS= read -r -d $'\0'; do
+ workflow_files+=("$REPLY")
+ done < <(find .gitea/workflows -name "*.yml" -print0 2>/dev/null)
+
+ for file in "${workflow_files[@]}"; do
+ echo -e "\nChecking inputs in $file..."
+
+ # Extract input definitions and usage
+ local input_definitions=()
+ while IFS= read -r line; do
+ if [[ $line =~ ^[[:space:]]*([a-zA-Z0-9_-]+):[[:space:]]*$ ]]; then
+ input_name="${BASH_REMATCH[1]}"
+ if [[ ! $input_name =~ ^(description|required|type|default)$ ]]; then
+ input_definitions+=("$input_name")
+ fi
+ fi
+ done < <(sed -n '/inputs:/,/^[[:space:]]*[a-zA-Z]/p' "$file" | head -n -1)
+
+ # Check if inputs are used in the workflow
+ for input in "${input_definitions[@]}"; do
+ if grep -q "\${{ inputs\.$input }}" "$file"; then
+ print_status 0 "Input '$input' is used in workflow"
+ else
+ print_warning "Input '$input' is defined but not used"
+ fi
+ done
+ done
+}
+
+# Create test project structure
+create_test_project() {
+ echo -e "\n${BLUE}Creating test project structure...${NC}"
+
+ local test_dir="test-validation-project"
+
+ if [ -d "$test_dir" ]; then
+ print_info "Removing existing test project"
+ rm -rf "$test_dir"
+ fi
+
+ mkdir -p "$test_dir/src/test_package"
+ cd "$test_dir"
+
+ # Create pyproject.toml
+ cat > pyproject.toml << 'EOF'
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "test-validation-package"
+version = "0.1.0"
+description = "Test package for CI template validation"
+dependencies = []
+
+[project.optional-dependencies]
+dev = [
+ "mypy>=1.0.0",
+ "ruff>=0.1.0",
+ "pyupgrade>=3.0.0",
+ "pytest>=7.0.0",
+ "pytest-cov>=4.0.0",
+]
+
+[tool.ruff]
+line-length = 88
+target-version = "py310"
+
+[tool.mypy]
+python_version = "3.10"
+warn_return_any = true
+warn_unused_configs = true
+EOF
+
+ # Create source code
+ mkdir -p src/test_package
+ cat > src/test_package/__init__.py << 'EOF'
+"""Test package for validation."""
+__version__ = "0.1.0"
+
+def hello_world() -> str:
+ """Return a greeting message."""
+ return "Hello, World!"
+EOF
+
+ cat > src/test_package/math_utils.py << 'EOF'
+"""Math utility functions."""
+from typing import List
+
+def add_numbers(numbers: List[float]) -> float:
+ """Add a list of numbers."""
+ return sum(numbers)
+
+def multiply_numbers(numbers: List[float]) -> float:
+ """Multiply a list of numbers."""
+ result = 1.0
+ for num in numbers:
+ result *= num
+ return result
+EOF
+
+ # Create tests
+ mkdir -p tests
+ cat > tests/test_math_utils.py << 'EOF'
+"""Tests for math utilities."""
+import pytest
+from test_package.math_utils import add_numbers, multiply_numbers
+
+def test_add_numbers():
+ assert add_numbers([1, 2, 3]) == 6
+ assert add_numbers([]) == 0
+ assert add_numbers([5.5, 2.5]) == 8.0
+
+def test_multiply_numbers():
+ assert multiply_numbers([2, 3, 4]) == 24
+ assert multiply_numbers([1]) == 1
+ assert multiply_numbers([2.5, 2]) == 5.0
+EOF
+
+ cd ..
+ print_status 0 "Test project created at $test_dir"
+}
+
+# Generate validation report
+generate_report() {
+ echo -e "\n${BLUE}Generating validation report...${NC}"
+
+ local report_file="validation-report.md"
+
+ cat > "$report_file" << 'EOF'
+# CI Template Validation Report
+
+Generated on: $(date)
+
+## Summary
+
+This report contains the validation results for the CI template workflows.
+
+## Workflow Files
+
+EOF
+
+ # Add workflow file information
+ find .gitea/workflows -name "*.yml" 2>/dev/null | while read -r file; do
+ echo "### $file" >> "$report_file"
+ echo "" >> "$report_file"
+ echo "- **Size:** $(wc -c < "$file") bytes" >> "$report_file"
+ echo "- **Lines:** $(wc -l < "$file") lines" >> "$report_file"
+
+ if grep -q "workflow_call" "$file"; then
+ echo "- **Type:** Reusable workflow ✅" >> "$report_file"
+ else
+ echo "- **Type:** Standard workflow ⚠️" >> "$report_file"
+ fi
+
+ local input_count=$(grep -c "^[[:space:]]*[a-zA-Z0-9_-]*:[[:space:]]*$" "$file" | head -n 1)
+ echo "- **Input parameters:** $input_count" >> "$report_file"
+ echo "" >> "$report_file"
+ done
+
+ print_status 0 "Validation report generated: $report_file"
+}
+
+# Main execution
+main() {
+ echo -e "${BLUE}Starting CI template validation...${NC}"
+
+ local exit_code=0
+
+ # Run all validation steps
+ check_dependencies || exit_code=1
+ validate_yaml_syntax || exit_code=1
+ validate_workflow_structure || exit_code=1
+ validate_input_parameters || exit_code=1
+ create_test_project || exit_code=1
+ generate_report || exit_code=1
+
+ echo -e "\n${BLUE}Validation Summary${NC}"
+ echo "=================="
+
+ if [ $exit_code -eq 0 ]; then
+ echo -e "${GREEN}🎉 All validations passed!${NC}"
+ echo -e "${GREEN}Your CI templates are ready for use.${NC}"
+ else
+ echo -e "${RED}💥 Some validations failed.${NC}"
+ echo -e "${RED}Please review the errors above and fix them.${NC}"
+ fi
+
+ echo -e "\n${BLUE}Next steps:${NC}"
+ echo "1. Review the validation report"
+ echo "2. Test the templates with the created test project"
+ echo "3. Run the automated test workflow: .gitea/workflows/test-templates.yml"
+
+ return $exit_code
+}
+
+# Run the main function
+main "$@"
\ No newline at end of file
diff --git a/test-projects/python-basic/.gitea/workflows/test-templates.yml b/test-projects/python-basic/.gitea/workflows/test-templates.yml
new file mode 100644
index 0000000..0323af1
--- /dev/null
+++ b/test-projects/python-basic/.gitea/workflows/test-templates.yml
@@ -0,0 +1,22 @@
+name: Test CI Templates
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+
+jobs:
+ code-quality:
+ name: Code Quality
+ uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/code-quality.yml@main
+ with:
+ python-version: "3.11"
+ working-directory: "."
+
+ test:
+ name: Run Tests
+ uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/pytest.yml@main
+ with:
+ python-version: "3.11"
+ coverage-package: "test_package"
+ pytest-args: "--verbose"
\ No newline at end of file
diff --git a/test-projects/python-basic/README.md b/test-projects/python-basic/README.md
new file mode 100644
index 0000000..13ed7a8
--- /dev/null
+++ b/test-projects/python-basic/README.md
@@ -0,0 +1,30 @@
+# Test Package
+
+A simple Python package for testing CI templates.
+
+## Features
+
+- Basic calculator operations
+- Utility functions
+- Comprehensive test suite
+- Type hints
+- Code quality tools integration
+
+## Development
+
+```bash
+# Install dependencies
+uv sync --all-extras
+
+# Run tests
+uv run pytest
+
+# Run code quality checks
+uv run mypy .
+uv run ruff check .
+uv run ruff format --check .
+```
+
+## Testing CI Templates
+
+This project is used to test the CI templates in the parent repository.
\ No newline at end of file
diff --git a/test-projects/python-basic/pyproject.toml b/test-projects/python-basic/pyproject.toml
new file mode 100644
index 0000000..cb880ad
--- /dev/null
+++ b/test-projects/python-basic/pyproject.toml
@@ -0,0 +1,82 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "test-package"
+version = "0.1.0"
+description = "Test package for CI template validation"
+authors = [
+ { name = "Test Author", email = "test@example.com" }
+]
+readme = "README.md"
+requires-python = ">=3.10"
+dependencies = [
+ "requests>=2.28.0",
+]
+
+[project.optional-dependencies]
+dev = [
+ "pytest>=7.0.0",
+ "pytest-cov>=4.0.0",
+ "mypy>=1.0.0",
+ "ruff>=0.1.0",
+ "pyupgrade>=3.0.0",
+]
+test = [
+ "pytest>=7.0.0",
+ "pytest-cov>=4.0.0",
+ "pytest-mock>=3.10.0",
+]
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+python_files = ["test_*.py", "*_test.py"]
+python_classes = ["Test*"]
+python_functions = ["test_*"]
+addopts = [
+ "--strict-markers",
+ "--strict-config",
+ "--verbose",
+ "--cov=test_package",
+ "--cov-report=term-missing",
+]
+
+[tool.mypy]
+python_version = "3.10"
+warn_return_any = true
+warn_unused_configs = true
+disallow_untyped_defs = true
+
+[tool.ruff]
+target-version = "py310"
+line-length = 88
+
+[tool.ruff.lint]
+select = [
+ "E", # pycodestyle errors
+ "W", # pycodestyle warnings
+ "F", # pyflakes
+ "I", # isort
+ "B", # flake8-bugbear
+ "C4", # flake8-comprehensions
+ "UP", # pyupgrade
+]
+
+[tool.coverage.run]
+source = ["test_package"]
+omit = ["tests/*"]
+
+[tool.coverage.report]
+exclude_lines = [
+ "pragma: no cover",
+ "def __repr__",
+ "if self.debug:",
+ "if settings.DEBUG",
+ "raise AssertionError",
+ "raise NotImplementedError",
+ "if 0:",
+ "if __name__ == .__main__.:",
+ "class .*\\bProtocol\\):",
+ "@(abc\\.)?abstractmethod",
+]
\ No newline at end of file
diff --git a/test-projects/python-basic/src/test_package/__init__.py b/test-projects/python-basic/src/test_package/__init__.py
new file mode 100644
index 0000000..a454d40
--- /dev/null
+++ b/test-projects/python-basic/src/test_package/__init__.py
@@ -0,0 +1,8 @@
+"""Test package for CI template validation."""
+
+__version__ = "0.1.0"
+
+from .calculator import Calculator
+from .utils import format_number
+
+__all__ = ["Calculator", "format_number"]
\ No newline at end of file
diff --git a/test-projects/python-basic/src/test_package/calculator.py b/test-projects/python-basic/src/test_package/calculator.py
new file mode 100644
index 0000000..d7eafd4
--- /dev/null
+++ b/test-projects/python-basic/src/test_package/calculator.py
@@ -0,0 +1,31 @@
+"""Calculator module for basic arithmetic operations."""
+
+from typing import Union
+
+Number = Union[int, float]
+
+
+class Calculator:
+ """A simple calculator class."""
+
+ def add(self, a: Number, b: Number) -> Number:
+ """Add two numbers."""
+ return a + b
+
+ def subtract(self, a: Number, b: Number) -> Number:
+ """Subtract b from a."""
+ return a - b
+
+ def multiply(self, a: Number, b: Number) -> Number:
+ """Multiply two numbers."""
+ return a * b
+
+ def divide(self, a: Number, b: Number) -> float:
+ """Divide a by b."""
+ if b == 0:
+ raise ValueError("Cannot divide by zero")
+ return a / b
+
+ def power(self, base: Number, exponent: Number) -> Number:
+ """Raise base to the power of exponent."""
+ return base ** exponent
\ No newline at end of file
diff --git a/test-projects/python-basic/src/test_package/utils.py b/test-projects/python-basic/src/test_package/utils.py
new file mode 100644
index 0000000..1f7d8df
--- /dev/null
+++ b/test-projects/python-basic/src/test_package/utils.py
@@ -0,0 +1,24 @@
+"""Utility functions for the test package."""
+
+from typing import Union
+
+
+def format_number(number: Union[int, float], precision: int = 2) -> str:
+ """Format a number with specified precision."""
+ if isinstance(number, int):
+ return str(number)
+ return f"{number:.{precision}f}"
+
+
+def is_even(number: int) -> bool:
+ """Check if a number is even."""
+ return number % 2 == 0
+
+
+def factorial(n: int) -> int:
+ """Calculate factorial of a number."""
+ if n < 0:
+ raise ValueError("Factorial is not defined for negative numbers")
+ if n == 0 or n == 1:
+ return 1
+ return n * factorial(n - 1)
\ No newline at end of file
diff --git a/test-projects/python-basic/tests/test_calculator.py b/test-projects/python-basic/tests/test_calculator.py
new file mode 100644
index 0000000..4578bfb
--- /dev/null
+++ b/test-projects/python-basic/tests/test_calculator.py
@@ -0,0 +1,47 @@
+"""Tests for the calculator module."""
+
+import pytest
+from test_package.calculator import Calculator
+
+
+class TestCalculator:
+ """Test cases for Calculator class."""
+
+ def setup_method(self):
+ """Set up test fixtures."""
+ self.calc = Calculator()
+
+ def test_add(self):
+ """Test addition operation."""
+ assert self.calc.add(2, 3) == 5
+ assert self.calc.add(-1, 1) == 0
+ assert self.calc.add(0.1, 0.2) == pytest.approx(0.3)
+
+ def test_subtract(self):
+ """Test subtraction operation."""
+ assert self.calc.subtract(5, 3) == 2
+ assert self.calc.subtract(1, 1) == 0
+ assert self.calc.subtract(-1, -2) == 1
+
+ def test_multiply(self):
+ """Test multiplication operation."""
+ assert self.calc.multiply(3, 4) == 12
+ assert self.calc.multiply(-2, 3) == -6
+ assert self.calc.multiply(0, 100) == 0
+
+ def test_divide(self):
+ """Test division operation."""
+ assert self.calc.divide(10, 2) == 5.0
+ assert self.calc.divide(7, 2) == 3.5
+ assert self.calc.divide(-6, 3) == -2.0
+
+ def test_divide_by_zero(self):
+ """Test division by zero raises error."""
+ with pytest.raises(ValueError, match="Cannot divide by zero"):
+ self.calc.divide(5, 0)
+
+ def test_power(self):
+ """Test power operation."""
+ assert self.calc.power(2, 3) == 8
+ assert self.calc.power(5, 0) == 1
+ assert self.calc.power(2, -1) == 0.5
\ No newline at end of file
diff --git a/test-projects/python-basic/tests/test_utils.py b/test-projects/python-basic/tests/test_utils.py
new file mode 100644
index 0000000..4150b69
--- /dev/null
+++ b/test-projects/python-basic/tests/test_utils.py
@@ -0,0 +1,41 @@
+"""Tests for the utils module."""
+
+import pytest
+from test_package.utils import format_number, is_even, factorial
+
+
+class TestUtils:
+ """Test cases for utility functions."""
+
+ def test_format_number_integer(self):
+ """Test formatting integers."""
+ assert format_number(42) == "42"
+ assert format_number(-5) == "-5"
+
+ def test_format_number_float(self):
+ """Test formatting floats."""
+ assert format_number(3.14159) == "3.14"
+ assert format_number(3.14159, 3) == "3.142"
+ assert format_number(2.0) == "2.00"
+
+ def test_is_even(self):
+ """Test even number detection."""
+ assert is_even(2) is True
+ assert is_even(4) is True
+ assert is_even(1) is False
+ assert is_even(3) is False
+ assert is_even(0) is True
+ assert is_even(-2) is True
+ assert is_even(-1) is False
+
+ def test_factorial_positive(self):
+ """Test factorial of positive numbers."""
+ assert factorial(0) == 1
+ assert factorial(1) == 1
+ assert factorial(5) == 120
+ assert factorial(3) == 6
+
+ def test_factorial_negative(self):
+ """Test factorial of negative numbers raises error."""
+ with pytest.raises(ValueError, match="Factorial is not defined for negative numbers"):
+ factorial(-1)
\ No newline at end of file
diff --git a/test-projects/web-basic/.gitea/workflows/test-formatting.yml b/test-projects/web-basic/.gitea/workflows/test-formatting.yml
new file mode 100644
index 0000000..6b3fec1
--- /dev/null
+++ b/test-projects/web-basic/.gitea/workflows/test-formatting.yml
@@ -0,0 +1,16 @@
+name: Test Web Formatting
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+
+jobs:
+ formatting:
+ name: Web Formatting
+ uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/web/formatting.yml@main
+ with:
+ node-version: "20"
+ prettier-config: ".prettierrc.json"
+ check-yaml: true
+ check-json: true
\ No newline at end of file
diff --git a/test-projects/web-basic/.prettierrc.json b/test-projects/web-basic/.prettierrc.json
new file mode 100644
index 0000000..feb3796
--- /dev/null
+++ b/test-projects/web-basic/.prettierrc.json
@@ -0,0 +1,7 @@
+{
+ "semi": true,
+ "trailingComma": "es5",
+ "singleQuote": true,
+ "printWidth": 80,
+ "tabWidth": 2
+}
\ No newline at end of file
diff --git a/test-projects/web-basic/config.yml b/test-projects/web-basic/config.yml
new file mode 100644
index 0000000..5dca919
--- /dev/null
+++ b/test-projects/web-basic/config.yml
@@ -0,0 +1,20 @@
+name: Test Web App
+description: A simple web application for testing
+
+api:
+ version: v1
+ endpoints:
+ - name: health
+ path: /health
+ method: GET
+ - name: calculator
+ path: /calculate
+ method: POST
+
+database:
+ type: sqlite
+ name: test.db
+
+logging:
+ level: info
+ format: json
\ No newline at end of file
diff --git a/test-projects/web-basic/index.js b/test-projects/web-basic/index.js
new file mode 100644
index 0000000..b1a6b17
--- /dev/null
+++ b/test-projects/web-basic/index.js
@@ -0,0 +1,15 @@
+console.log("Hello, World!");
+
+const calculator = {
+ add: (a, b) => a + b,
+ subtract: (a, b) => a - b,
+ multiply: (a, b) => a * b,
+ divide: (a, b) => {
+ if (b === 0) {
+ throw new Error("Cannot divide by zero");
+ }
+ return a / b;
+ },
+};
+
+module.exports = calculator;
\ No newline at end of file
diff --git a/test-projects/web-basic/package.json b/test-projects/web-basic/package.json
new file mode 100644
index 0000000..9580f01
--- /dev/null
+++ b/test-projects/web-basic/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "test-web-project",
+ "version": "1.0.0",
+ "description": "Test web project for CI template validation",
+ "main": "index.js",
+ "scripts": {
+ "start": "node index.js",
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "format": "prettier --write .",
+ "format:check": "prettier --check ."
+ },
+ "keywords": ["test", "ci", "templates"],
+ "author": "Test Author",
+ "license": "MIT",
+ "devDependencies": {
+ "prettier": "^3.0.0"
+ }
+}
\ No newline at end of file