diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5ec8e74..e092961 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -57,6 +57,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Install Python tools + run: pip install ruff + - name: Prepare test environment run: | chmod +x python/*.sh @@ -161,3 +164,68 @@ jobs: - name: Run YAML tests run: | for suite in tests/yml/*/run.sh; do "$suite"; done + + simple-tests: + name: Simple Tests + runs-on: ubuntu-latest + needs: shellcheck + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install codespell + run: pip install codespell + + - name: Prepare test environment + run: | + chmod +x simple/*.sh + chmod +x tests/lib/*.bash + find tests/simple -name "*.sh" -exec chmod +x {} + + + - name: Run Simple tests + run: | + for suite in tests/simple/*/run.sh; do "$suite"; done + + git-tests: + name: Git Tests + runs-on: ubuntu-latest + needs: shellcheck + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Prepare test environment + run: | + chmod +x git/*.sh + chmod +x tests/lib/*.bash + find tests/git -name "*.sh" -exec chmod +x {} + + + - name: Run Git tests + run: | + for suite in tests/git/*/run.sh; do "$suite"; done + + shell-tests: + name: Shell Tests + runs-on: ubuntu-latest + needs: shellcheck + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install shfmt + run: | + curl -sS https://webi.sh/shfmt | sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Prepare test environment + run: | + chmod +x shell/*.sh + chmod +x tests/lib/*.bash + find tests/shell -name "*.sh" -exec chmod +x {} + + + - name: Run Shell tests + run: | + for suite in tests/shell/*/run.sh; do "$suite"; done diff --git a/.gitignore b/.gitignore index 8770716..a0e3968 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ tests/**/tmp/ # Logs *.log -articles/ \ No newline at end of file +articles/ +tasks/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 8eaa7b2..4dd4a1d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ ## Project Overview -A collection of reusable git hook scripts for automating code quality checks. Covers JavaScript/TypeScript, Python, PHP/Laravel, CSS, HTML, Markdown, YAML, Docker, and shell scripts. +A collection of reusable git hook scripts for automating code quality checks. Covers JavaScript/TypeScript, Python, PHP/Laravel, CSS, HTML, Markdown, YAML, Docker, shell scripts, and general-purpose checks (spelling, JSON, secret detection, shell formatting). ## Directory Structure @@ -16,6 +16,8 @@ docker/ # Docker-related hooks git/ # Git workflow hooks ├── check_branch_name.sh # Branch naming convention validator +├── check_gitleaks.sh # Secret detection in staged changes (optional) +├── check_gitleaks_all.sh # Secret detection in full repository history └── preparations/ # Commit message enhancers ├── add_task_id_in_commit.sh └── prepare-commit-description.sh @@ -54,10 +56,20 @@ python/ # Python hooks ├── check_mypy_in_docker.sh # Mypy (Docker) ├── check_pytest.sh # Pytest (local) ├── check_pytest_in_docker.sh # Pytest (Docker) +├── check_ruff.sh # Ruff linter for staged files +├── check_ruff_all.sh # Ruff linter for entire project └── find_test.sh # Service test existence validator shell/ # Shell script validation -└── check_shellcheck.sh # ShellCheck via Docker +├── check_shellcheck.sh # ShellCheck via Docker +├── check_shfmt.sh # shfmt formatting check for staged files +└── check_shfmt_all.sh # shfmt formatting check for entire project + +simple/ # General-purpose hooks +├── check_codespell.sh # Spelling check for staged files +├── check_codespell_all.sh # Spelling check for entire project +├── check_json_validate.sh # JSON syntax validation for staged files +└── check_json_validate_all.sh # JSON syntax validation for entire project scripts/ # Local utility scripts ├── check_shellcheck.sh # ShellCheck (local execution) @@ -71,11 +83,14 @@ tests/ # Test framework ├── run_all.sh # Run all test suites ├── lib/test_helper.bash # Test utilities and assertions ├── css/ # CSS hook tests +├── git/ # Git hook tests (gitleaks) ├── html/ # HTML hook tests ├── javascript/ # JavaScript hook tests ├── markdown/ # Markdown hook tests ├── php/ # PHP hook tests -├── python/ # Python hook tests +├── python/ # Python hook tests (flake8, mypy, ruff, pytest, find_test) +├── shell/ # Shell hook tests (shfmt) +├── simple/ # Simple hook tests (codespell, json_validate) └── yml/ # YAML hook tests ``` @@ -112,11 +127,15 @@ Every script must start with a description block after the shebang: - Bash 4.0+, Git, jq - Node.js with npx (for JavaScript/TypeScript, CSS, HTML hooks) - Python 3, Flake8, Mypy, Pytest (for Python hooks) +- Ruff (`pip install ruff`) for `python/check_ruff.sh` +- Codespell (`pip install codespell`) for `simple/check_codespell.sh` - PHP 8.1+, Composer (for PHPStan/Pint) - Stylelint via npx (for CSS hooks) - HTMLHint via npx (for HTML hooks) - markdownlint-cli (for Markdown hooks) - yamllint (for YAML hooks) +- shfmt (`go install mvdan.cc/sh/v3/cmd/shfmt@latest` or `brew install shfmt`) for `shell/check_shfmt.sh` +- gitleaks (optional, see [install guide](https://github.com/gitleaks/gitleaks#installing)) for `git/check_gitleaks.sh` - Docker (optional, for containerized checks) - ShellCheck (for shell validation) diff --git a/README.md b/README.md index 7098442..edf305c 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,16 @@ A collection of reusable git hook scripts for automating code quality checks and ## Features - **JavaScript/TypeScript**: ESLint, Prettier, TypeScript type checking, Vitest test runner, test existence validation -- **Python**: Flake8 linting, Mypy static analysis, Pytest runner, service test validation (local and Docker modes) +- **Python**: Flake8 linting, Mypy static analysis, Ruff linting, Pytest runner, service test validation (local and Docker modes) - **PHP/Laravel**: PHPStan analysis with progressive error reduction, Pint code style fixing, test coverage validation - **CSS/SCSS/Less**: Stylelint code style validation - **HTML**: HTMLHint linting - **Markdown**: markdownlint style validation - **YAML**: yamllint style validation - **Docker**: Dockerfile linting with Hadolint -- **Shell**: Script validation with ShellCheck -- **Git**: Branch naming conventions, automatic task ID injection in commits +- **Shell**: Script validation with ShellCheck, shfmt formatting check +- **Git**: Branch naming conventions, automatic task ID injection in commits, secret detection with gitleaks +- **Simple**: Spelling check with codespell, JSON syntax validation ## Quick Start @@ -48,6 +49,8 @@ A collection of reusable git hook scripts for automating code quality checks and | `python/check_mypy_in_docker.sh` | Runs Mypy inside the Docker container, maps host/container paths. | | `python/check_pytest.sh` | Runs the full Pytest suite locally with `PYTHONPATH=./app`. | | `python/check_pytest_in_docker.sh` | Runs Pytest inside the Docker container, converts container paths to host paths. | +| `python/check_ruff.sh` | Runs Ruff linter on staged Python files passed as arguments. | +| `python/check_ruff_all.sh` | Runs Ruff linter on all Python files in the project. | | `python/find_test.sh` | Validates that each service in `app/services/` has exactly one corresponding test file. | ### PHP @@ -92,6 +95,8 @@ A collection of reusable git hook scripts for automating code quality checks and | Script | Description | |--------|-------------| | `git/check_branch_name.sh` | Validates branch names match pattern: `{type}/{task-id}_{description}` | +| `git/check_gitleaks.sh` | Scans staged changes for secrets using gitleaks (optional; exits 0 if not installed). | +| `git/check_gitleaks_all.sh` | Scans full repository history for secrets using gitleaks. | | `git/preparations/add_task_id_in_commit.sh` | Prepends task ID from branch name to commit message. | | `git/preparations/prepare-commit-description.sh` | Appends list of changed files to commit message. | @@ -106,8 +111,19 @@ A collection of reusable git hook scripts for automating code quality checks and | Script | Description | |--------|-------------| | `shell/check_shellcheck.sh` | Validates shell scripts using ShellCheck (via Docker). | +| `shell/check_shfmt.sh` | Checks shell script formatting using shfmt (`-i 2 -ci`). | +| `shell/check_shfmt_all.sh` | Checks formatting of all `.sh` files in the project. | | `scripts/check_shellcheck.sh` | Validates shell scripts using ShellCheck (local). | +### Simple + +| Script | Description | +|--------|-------------| +| `simple/check_codespell.sh` | Checks files for spelling mistakes using codespell. | +| `simple/check_codespell_all.sh` | Checks all project files for spelling mistakes. | +| `simple/check_json_validate.sh` | Validates JSON files for syntax correctness using `python3 -m json.tool`. | +| `simple/check_json_validate_all.sh` | Validates all `*.json` files in the project. | + ## Usage Examples ### JavaScript/TypeScript @@ -210,6 +226,56 @@ A collection of reusable git hook scripts for automating code quality checks and ./yml/check_yamllint_all.sh ``` +### Ruff + +```bash +# Check staged files +./python/check_ruff.sh app/services/user.py app/models/auth.py + +# Check all Python files in the project +./python/check_ruff_all.sh +``` + +### Gitleaks + +```bash +# Check staged changes for secrets (optional: exits 0 if gitleaks not installed) +./git/check_gitleaks.sh + +# Scan full repository history for secrets +./git/check_gitleaks_all.sh +``` + +### shfmt + +```bash +# Check staged shell scripts for formatting +./shell/check_shfmt.sh deploy.sh scripts/setup.sh + +# Check all shell scripts in the project +./shell/check_shfmt_all.sh +``` + +### Codespell + +```bash +# Check specific files for spelling mistakes +./simple/check_codespell.sh README.md src/utils.py + +# Check all project files +./simple/check_codespell_all.sh +``` + +### JSON Validation + +```bash +# Validate specific JSON files +./simple/check_json_validate.sh package.json .eslintrc.json + +# Validate all JSON files in the project +./simple/check_json_validate_all.sh +``` + ### Branch Name Validation ```bash @@ -290,6 +356,7 @@ SH_FILES=$(echo "$FILES" | grep '\.sh$' || true) ./tests/javascript/vitest/run.sh ./tests/python/flake8/run.sh ./tests/python/mypy/run.sh +./tests/python/ruff/run.sh ./tests/python/find_test/run.sh ./tests/css/stylelint/run.sh ./tests/css/stylelint_all/run.sh @@ -299,6 +366,10 @@ SH_FILES=$(echo "$FILES" | grep '\.sh$' || true) ./tests/markdown/markdownlint_all/run.sh ./tests/yml/yamllint/run.sh ./tests/yml/yamllint_all/run.sh +./tests/git/gitleaks/run.sh +./tests/shell/shfmt/run.sh +./tests/simple/codespell/run.sh +./tests/simple/json_validate/run.sh ``` ## Requirements @@ -308,11 +379,15 @@ SH_FILES=$(echo "$FILES" | grep '\.sh$' || true) - jq - Node.js with npx (for JavaScript/TypeScript, CSS, HTML hooks) - Python 3, Flake8, Mypy, Pytest (for Python hooks) +- Ruff (`pip install ruff`) for `python/check_ruff.sh` +- Codespell (`pip install codespell`) for `simple/check_codespell.sh` - PHP 8.1+ with Composer (for PHP hooks) - Stylelint (`npm install -g stylelint`) or via `npx` - HTMLHint (`npm install -g htmlhint`) or via `npx` - markdownlint-cli (`npm install -g markdownlint-cli`) - yamllint (`pip install yamllint` or system package) +- shfmt (`go install mvdan.cc/sh/v3/cmd/shfmt@latest` or `brew install shfmt`) for `shell/check_shfmt.sh` +- gitleaks (optional, see [install guide](https://github.com/gitleaks/gitleaks#installing)) for `git/check_gitleaks.sh` - Docker (optional, for Docker-based hooks) - ShellCheck (for shell validation) diff --git a/git/check_gitleaks.sh b/git/check_gitleaks.sh new file mode 100755 index 0000000..93f02f3 --- /dev/null +++ b/git/check_gitleaks.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Scans staged changes for secrets and tokens using gitleaks. +# Runs gitleaks protect --staged --redact --exit-code 1. +# Exits 0 with a warning if gitleaks is not installed (optional hook). +# Exits 1 if any secrets are detected; exits 0 on clean scan. +# ------------------------------------------------------------------------------ + +if ! command -v gitleaks > /dev/null 2>&1; then + echo "WARNING: gitleaks is not installed. Skipping secret detection." + echo "Install it with: https://github.com/gitleaks/gitleaks#installing" + exit 0 +fi + +OUTPUT=$(gitleaks protect --staged --redact --exit-code 1 2>&1) +EXIT_CODE=$? + +if [ $EXIT_CODE -ne 0 ]; then + echo "ERROR: gitleaks detected secrets in staged changes!" + echo "$OUTPUT" + echo "Remove the secrets above before committing." + exit 1 +fi + +echo "gitleaks: no secrets detected in staged changes." +exit 0 diff --git a/git/check_gitleaks_all.sh b/git/check_gitleaks_all.sh new file mode 100755 index 0000000..5c8de59 --- /dev/null +++ b/git/check_gitleaks_all.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Scans the entire repository history for secrets using gitleaks detect. +# Runs gitleaks detect --redact --exit-code 1 on the full git history. +# Exits 0 with a warning if gitleaks is not installed (optional hook). +# Exits 1 if any secrets are detected; exits 0 on clean scan. +# ------------------------------------------------------------------------------ + +if ! command -v gitleaks > /dev/null 2>&1; then + echo "WARNING: gitleaks is not installed. Skipping secret detection." + echo "Install it with: https://github.com/gitleaks/gitleaks#installing" + exit 0 +fi + +OUTPUT=$(gitleaks detect --redact --exit-code 1 2>&1) +EXIT_CODE=$? + +if [ $EXIT_CODE -ne 0 ]; then + echo "ERROR: gitleaks detected secrets in repository history!" + echo "$OUTPUT" + exit 1 +fi + +echo "gitleaks: no secrets detected in repository history." +exit 0 diff --git a/git/preparations/prepare-commit-description.sh b/git/preparations/prepare-commit-description.sh index 7bcbdd0..97ad417 100644 --- a/git/preparations/prepare-commit-description.sh +++ b/git/preparations/prepare-commit-description.sh @@ -23,6 +23,7 @@ PROMPT="git-agent desc commit en $STAGED_FILES" COMMIT_TEXT=$(printf "%s\n" "$PROMPT" | claude -p) # Remove possible triple backticks ``` and empty lines +# shellcheck disable=SC2016 COMMIT_TEXT=$(echo "$COMMIT_TEXT" | sed 's/^```//; s/```$//' | sed '/^$/d') # Exit if the generated text is empty diff --git a/python/check_ruff.sh b/python/check_ruff.sh new file mode 100755 index 0000000..bc1a7c1 --- /dev/null +++ b/python/check_ruff.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Checks Python files for style and logic errors using ruff. +# Accepts file paths as arguments; skips non-.py files and missing files. +# Exits 1 with an install hint if ruff is not installed. +# Exits 1 if ruff reports any issues; exits 0 on success. +# ------------------------------------------------------------------------------ + +if [ $# -eq 0 ]; then + echo "No files to check" + exit 0 +fi + +if ! command -v ruff > /dev/null 2>&1; then + echo "ERROR: ruff is not installed." + echo "Install it with: pip install ruff" + exit 1 +fi + +PY_FILES=() +CHECKED_FILES=0 +HAS_ERRORS=0 + +for file in "$@"; do + if [[ ! "$file" =~ \.py$ ]]; then + continue + fi + + if [ ! -f "$file" ]; then + continue + fi + + PY_FILES+=("$file") +done + +if [ ${#PY_FILES[@]} -eq 0 ]; then + echo "No Python files to check" + exit 0 +fi + +for file in "${PY_FILES[@]}"; do + CHECKED_FILES=$((CHECKED_FILES + 1)) + + OUTPUT=$(ruff check "$file" 2>&1) + EXIT_CODE=$? + + if [ $EXIT_CODE -ne 0 ]; then + HAS_ERRORS=1 + echo "ruff errors in: $file" + echo "$OUTPUT" + echo "" + fi +done + +if [ $HAS_ERRORS -ne 0 ]; then + echo "----------------------------------------" + echo "ERROR: ruff check failed!" + echo "Total files checked: $CHECKED_FILES" + echo "Fix the errors above before committing." + exit 1 +fi + +echo "ruff check passed! ($CHECKED_FILES files checked)" +exit 0 diff --git a/python/check_ruff_all.sh b/python/check_ruff_all.sh new file mode 100755 index 0000000..1131e25 --- /dev/null +++ b/python/check_ruff_all.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Checks all Python files in the project for style and logic errors using ruff. +# Scans repository root, excluding .git/, node_modules/, _site/, tasks/. +# Delegates to check_ruff.sh with the discovered file list. +# Exits 0 if no files found or all pass; exits 1 if any errors found. +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +mapfile -t PY_FILES < <(find "$PROJECT_DIR" -type f -name "*.py" \ + -not -path "*/.git/*" \ + -not -path "*/node_modules/*" \ + -not -path "*/_site/*" \ + -not -path "*/tasks/*") + +if [ ${#PY_FILES[@]} -eq 0 ]; then + echo "No Python files found" + exit 0 +fi + +exec "$SCRIPT_DIR/check_ruff.sh" "${PY_FILES[@]}" diff --git a/shell/check_shfmt.sh b/shell/check_shfmt.sh new file mode 100755 index 0000000..48354a3 --- /dev/null +++ b/shell/check_shfmt.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Checks shell scripts for formatting issues using shfmt. +# Accepts file paths as arguments; skips non-.sh files and missing files. +# Exits 1 with an install hint if shfmt is not installed. +# Exits 1 and shows a diff if any script is not properly formatted. +# ------------------------------------------------------------------------------ + +if [ $# -eq 0 ]; then + echo "No files to check" + exit 0 +fi + +if ! command -v shfmt > /dev/null 2>&1; then + echo "ERROR: shfmt is not installed." + echo "Install it with: go install mvdan.cc/sh/v3/cmd/shfmt@latest" + echo "Or: brew install shfmt" + exit 1 +fi + +SH_FILES=() +CHECKED_FILES=0 +HAS_ERRORS=0 + +for file in "$@"; do + if [[ ! "$file" =~ \.sh$ ]]; then + continue + fi + + if [ ! -f "$file" ]; then + continue + fi + + SH_FILES+=("$file") +done + +if [ ${#SH_FILES[@]} -eq 0 ]; then + echo "No shell files to check" + exit 0 +fi + +for file in "${SH_FILES[@]}"; do + CHECKED_FILES=$((CHECKED_FILES + 1)) + + OUTPUT=$(shfmt -d -i 2 -ci "$file" 2>&1) + EXIT_CODE=$? + + if [ $EXIT_CODE -ne 0 ] || [ -n "$OUTPUT" ]; then + HAS_ERRORS=1 + echo "shfmt formatting issues in: $file" + echo "$OUTPUT" + echo "" + fi +done + +if [ $HAS_ERRORS -ne 0 ]; then + echo "----------------------------------------" + echo "ERROR: shfmt check failed!" + echo "Total files checked: $CHECKED_FILES" + echo "Run 'shfmt -w -i 2 -ci ' to auto-fix." + exit 1 +fi + +echo "shfmt check passed! ($CHECKED_FILES files checked)" +exit 0 diff --git a/shell/check_shfmt_all.sh b/shell/check_shfmt_all.sh new file mode 100755 index 0000000..6a5c148 --- /dev/null +++ b/shell/check_shfmt_all.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Checks all shell scripts in the project for formatting issues using shfmt. +# Scans repository root, excluding .git/, node_modules/, _site/, tasks/. +# Delegates to check_shfmt.sh with the discovered file list. +# Exits 0 if no files found or all pass; exits 1 if any errors found. +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +mapfile -t SH_FILES < <(find "$PROJECT_DIR" -type f -name "*.sh" \ + -not -path "*/.git/*" \ + -not -path "*/node_modules/*" \ + -not -path "*/_site/*" \ + -not -path "*/tasks/*") + +if [ ${#SH_FILES[@]} -eq 0 ]; then + echo "No shell files found" + exit 0 +fi + +exec "$SCRIPT_DIR/check_shfmt.sh" "${SH_FILES[@]}" diff --git a/simple/check_codespell.sh b/simple/check_codespell.sh new file mode 100755 index 0000000..18c82b4 --- /dev/null +++ b/simple/check_codespell.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Checks files for spelling mistakes using codespell. +# Accepts file paths as arguments; skips files that don't exist. +# Exits 1 if any spelling errors are found; exits 0 on success. +# Exits 1 with an install hint if codespell is not installed. +# ------------------------------------------------------------------------------ + +if [ $# -eq 0 ]; then + echo "No files to check" + exit 0 +fi + +if ! command -v codespell > /dev/null 2>&1; then + echo "ERROR: codespell is not installed." + echo "Install it with: pip install codespell" + exit 1 +fi + +FILES=() + +for file in "$@"; do + if [ ! -f "$file" ]; then + continue + fi + FILES+=("$file") +done + +if [ ${#FILES[@]} -eq 0 ]; then + echo "No files to check" + exit 0 +fi + +OUTPUT=$(codespell "${FILES[@]}" 2>&1) +EXIT_CODE=$? + +if [ $EXIT_CODE -ne 0 ]; then + echo "ERROR: Spelling check failed!" + echo "$OUTPUT" + echo "Fix the spelling errors above before committing." + exit 1 +fi + +echo "Spelling check passed! (${#FILES[@]} files checked)" +exit 0 diff --git a/simple/check_codespell_all.sh b/simple/check_codespell_all.sh new file mode 100755 index 0000000..bc15808 --- /dev/null +++ b/simple/check_codespell_all.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Checks all files in the project for spelling mistakes using codespell. +# Scans repository root, excluding .git/, node_modules/, _site/, tasks/. +# Delegates to check_codespell.sh with the discovered file list. +# Exits 0 if no files found or all pass; exits 1 if any errors found. +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +mapfile -t FILES < <(find "$PROJECT_DIR" -type f \ + -not -path "*/.git/*" \ + -not -path "*/node_modules/*" \ + -not -path "*/_site/*" \ + -not -path "*/tasks/*") + +if [ ${#FILES[@]} -eq 0 ]; then + echo "No files found" + exit 0 +fi + +exec "$SCRIPT_DIR/check_codespell.sh" "${FILES[@]}" diff --git a/simple/check_json_validate.sh b/simple/check_json_validate.sh new file mode 100755 index 0000000..f064759 --- /dev/null +++ b/simple/check_json_validate.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Validates JSON files for syntax correctness using python3 -m json.tool. +# Accepts file paths as arguments; skips non-.json files and missing files. +# Exits 1 if any file contains invalid JSON; exits 0 if all pass. +# ------------------------------------------------------------------------------ + +if [ $# -eq 0 ]; then + echo "No files to check" + exit 0 +fi + +JSON_FILES=() +CHECKED_FILES=0 +HAS_ERRORS=0 + +for file in "$@"; do + if [[ ! "$file" =~ \.json$ ]]; then + continue + fi + + if [ ! -f "$file" ]; then + continue + fi + + JSON_FILES+=("$file") +done + +if [ ${#JSON_FILES[@]} -eq 0 ]; then + echo "No JSON files to check" + exit 0 +fi + +for file in "${JSON_FILES[@]}"; do + CHECKED_FILES=$((CHECKED_FILES + 1)) + + if ! python3 -m json.tool "$file" > /dev/null 2>&1; then + HAS_ERRORS=1 + echo "JSON parse error in: $file" + python3 -m json.tool "$file" 2>&1 || true + echo "" + fi +done + +if [ $HAS_ERRORS -ne 0 ]; then + echo "----------------------------------------" + echo "ERROR: JSON validation failed!" + echo "Total files checked: $CHECKED_FILES" + echo "Fix the errors above before committing." + exit 1 +fi + +echo "JSON validation passed! ($CHECKED_FILES files checked)" +exit 0 diff --git a/simple/check_json_validate_all.sh b/simple/check_json_validate_all.sh new file mode 100755 index 0000000..4bfe8ba --- /dev/null +++ b/simple/check_json_validate_all.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Validates all JSON files in the project for syntax correctness. +# Scans repository root, excluding .git/, node_modules/, _site/, tasks/. +# Delegates to check_json_validate.sh with the discovered file list. +# Exits 0 if no files found or all pass; exits 1 if any errors found. +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +mapfile -t JSON_FILES < <(find "$PROJECT_DIR" -type f -name "*.json" \ + -not -path "*/.git/*" \ + -not -path "*/node_modules/*" \ + -not -path "*/_site/*" \ + -not -path "*/tasks/*") + +if [ ${#JSON_FILES[@]} -eq 0 ]; then + echo "No JSON files found" + exit 0 +fi + +exec "$SCRIPT_DIR/check_json_validate.sh" "${JSON_FILES[@]}" diff --git a/tests/git/gitleaks/cases/01_binary_missing_exits_zero.sh b/tests/git/gitleaks/cases/01_binary_missing_exits_zero.sh new file mode 100755 index 0000000..25e2396 --- /dev/null +++ b/tests/git/gitleaks/cases/01_binary_missing_exits_zero.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: gitleaks binary missing exits with 0 and warning (optional hook) +# Expected: exit 0, warning message about gitleaks not installed +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "gitleaks_binary_missing" + +HOOK_PATH="$(cd "$SCRIPT_DIR/../../../../git" && pwd)/check_gitleaks.sh" + +SAVED_PATH="$PATH" +export PATH="/usr/bin:/bin" + +set +e +# shellcheck disable=SC2034 +LAST_OUTPUT=$("$HOOK_PATH" 2>&1) +# shellcheck disable=SC2034 +LAST_EXIT_CODE=$? +set -e + +export PATH="$SAVED_PATH" + +assert_exit_code 0 +assert_output_contains "WARNING: gitleaks is not installed" + +test_passed diff --git a/tests/git/gitleaks/cases/02_clean_repo_passes.sh b/tests/git/gitleaks/cases/02_clean_repo_passes.sh new file mode 100755 index 0000000..d50f805 --- /dev/null +++ b/tests/git/gitleaks/cases/02_clean_repo_passes.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Clean staged changes pass gitleaks scan +# Expected: exit 0, success message +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "gitleaks_clean_repo" + +# Mock gitleaks to succeed +mkdir -p "$TEST_DIR/bin" +cat > "$TEST_DIR/bin/gitleaks" << 'EOF' +#!/bin/bash +exit 0 +EOF +chmod +x "$TEST_DIR/bin/gitleaks" +export PATH="$TEST_DIR/bin:$PATH" + +run_hook "git/check_gitleaks.sh" + +assert_exit_code 0 +assert_output_contains "no secrets detected" + +test_passed diff --git a/tests/git/gitleaks/cases/03_secrets_detected_fails.sh b/tests/git/gitleaks/cases/03_secrets_detected_fails.sh new file mode 100755 index 0000000..6a6bbec --- /dev/null +++ b/tests/git/gitleaks/cases/03_secrets_detected_fails.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Secrets in staged changes cause gitleaks to fail +# Expected: exit 1, error message about detected secrets +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "gitleaks_secrets_detected" + +# Mock gitleaks to fail with detected secrets +mkdir -p "$TEST_DIR/bin" +cat > "$TEST_DIR/bin/gitleaks" << 'EOF' +#!/bin/bash +echo "secret detected: REDACTED" +exit 1 +EOF +chmod +x "$TEST_DIR/bin/gitleaks" +export PATH="$TEST_DIR/bin:$PATH" + +run_hook "git/check_gitleaks.sh" + +assert_exit_code 1 +assert_output_contains "ERROR: gitleaks detected secrets" + +test_passed diff --git a/tests/git/gitleaks/cases/04_all_variant_binary_missing_exits_zero.sh b/tests/git/gitleaks/cases/04_all_variant_binary_missing_exits_zero.sh new file mode 100755 index 0000000..27f2326 --- /dev/null +++ b/tests/git/gitleaks/cases/04_all_variant_binary_missing_exits_zero.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: gitleaks binary missing in _all variant exits with 0 and warning +# Expected: exit 0, warning message about gitleaks not installed +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "gitleaks_all_binary_missing" + +HOOK_PATH="$(cd "$SCRIPT_DIR/../../../../git" && pwd)/check_gitleaks_all.sh" + +SAVED_PATH="$PATH" +export PATH="/usr/bin:/bin" + +set +e +# shellcheck disable=SC2034 +LAST_OUTPUT=$("$HOOK_PATH" 2>&1) +# shellcheck disable=SC2034 +LAST_EXIT_CODE=$? +set -e + +export PATH="$SAVED_PATH" + +assert_exit_code 0 +assert_output_contains "WARNING: gitleaks is not installed" + +test_passed diff --git a/tests/git/gitleaks/run.sh b/tests/git/gitleaks/run.sh new file mode 100755 index 0000000..6fa42de --- /dev/null +++ b/tests/git/gitleaks/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Runs all gitleaks hook test cases. +# Executes each case script in the cases/ directory. +# ------------------------------------------------------------------------------ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PASSED=0 +FAILED=0 + +for case_file in "$SCRIPT_DIR"/cases/*.sh; do + [[ -f "$case_file" ]] || continue + + if "$case_file"; then + ((PASSED++)) || true + else + ((FAILED++)) || true + fi +done + +echo "" +echo "Gitleaks tests: $PASSED passed, $FAILED failed" + +[[ $FAILED -eq 0 ]] diff --git a/tests/python/ruff/cases/01_no_files_exits_zero.sh b/tests/python/ruff/cases/01_no_files_exits_zero.sh new file mode 100755 index 0000000..606e1c5 --- /dev/null +++ b/tests/python/ruff/cases/01_no_files_exits_zero.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: No files provided exits with 0 +# Expected: exit 0, message about no files +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "ruff_no_files" + +run_hook "python/check_ruff.sh" + +assert_exit_code 0 +assert_output_contains "No files to check" + +test_passed diff --git a/tests/python/ruff/cases/02_binary_missing_exits_one.sh b/tests/python/ruff/cases/02_binary_missing_exits_one.sh new file mode 100755 index 0000000..741b7b9 --- /dev/null +++ b/tests/python/ruff/cases/02_binary_missing_exits_one.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: ruff binary missing exits with 1 and install hint +# Expected: exit 1, message about installation +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "ruff_binary_missing" + +HOOK_PATH="$(cd "$SCRIPT_DIR/../../../../python" && pwd)/check_ruff.sh" + +create_python_fixture "app/module.py" "MyModule" + +SAVED_PATH="$PATH" +export PATH="/usr/bin:/bin" + +set +e +# shellcheck disable=SC2034 +LAST_OUTPUT=$("$HOOK_PATH" "app/module.py" 2>&1) +# shellcheck disable=SC2034 +LAST_EXIT_CODE=$? +set -e + +export PATH="$SAVED_PATH" + +assert_exit_code 1 +assert_output_contains "ruff is not installed" + +test_passed diff --git a/tests/python/ruff/cases/03_clean_file_passes.sh b/tests/python/ruff/cases/03_clean_file_passes.sh new file mode 100755 index 0000000..da41e09 --- /dev/null +++ b/tests/python/ruff/cases/03_clean_file_passes.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Clean Python file with no ruff errors passes +# Expected: exit 0, success message +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "ruff_clean_file" + +mkdir -p "$TEST_DIR/bin" +cat > "$TEST_DIR/bin/ruff" << 'EOF' +#!/bin/bash +exit 0 +EOF +chmod +x "$TEST_DIR/bin/ruff" +export PATH="$TEST_DIR/bin:$PATH" + +create_python_fixture "app/clean.py" "CleanClass" + +run_hook "python/check_ruff.sh" "app/clean.py" + +assert_exit_code 0 +assert_output_contains "ruff check passed" + +test_passed diff --git a/tests/python/ruff/cases/04_file_with_errors_fails.sh b/tests/python/ruff/cases/04_file_with_errors_fails.sh new file mode 100755 index 0000000..9520515 --- /dev/null +++ b/tests/python/ruff/cases/04_file_with_errors_fails.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Python file with ruff errors fails +# Expected: exit 1, error message about ruff check +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "ruff_file_with_errors" + +mkdir -p "$TEST_DIR/bin" +cat > "$TEST_DIR/bin/ruff" << 'EOF' +#!/bin/bash +echo "app/bad.py:1:1: E302 expected 2 blank lines" +exit 1 +EOF +chmod +x "$TEST_DIR/bin/ruff" +export PATH="$TEST_DIR/bin:$PATH" + +create_python_fixture "app/bad.py" "BadClass" + +run_hook "python/check_ruff.sh" "app/bad.py" + +assert_exit_code 1 +assert_output_contains "ERROR: ruff check failed" + +test_passed diff --git a/tests/python/ruff/run.sh b/tests/python/ruff/run.sh new file mode 100755 index 0000000..d5c9c3f --- /dev/null +++ b/tests/python/ruff/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Runs all ruff hook test cases. +# Executes each case script in the cases/ directory. +# ------------------------------------------------------------------------------ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PASSED=0 +FAILED=0 + +for case_file in "$SCRIPT_DIR"/cases/*.sh; do + [[ -f "$case_file" ]] || continue + + if "$case_file"; then + ((PASSED++)) || true + else + ((FAILED++)) || true + fi +done + +echo "" +echo "Ruff tests: $PASSED passed, $FAILED failed" + +[[ $FAILED -eq 0 ]] diff --git a/tests/shell/shfmt/cases/01_no_files_exits_zero.sh b/tests/shell/shfmt/cases/01_no_files_exits_zero.sh new file mode 100755 index 0000000..ac82854 --- /dev/null +++ b/tests/shell/shfmt/cases/01_no_files_exits_zero.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: No files provided exits with 0 +# Expected: exit 0, message about no files +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "shfmt_no_files" + +run_hook "shell/check_shfmt.sh" + +assert_exit_code 0 +assert_output_contains "No files to check" + +test_passed diff --git a/tests/shell/shfmt/cases/02_binary_missing_exits_one.sh b/tests/shell/shfmt/cases/02_binary_missing_exits_one.sh new file mode 100755 index 0000000..de791c2 --- /dev/null +++ b/tests/shell/shfmt/cases/02_binary_missing_exits_one.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: shfmt binary missing exits with 1 and install hint +# Expected: exit 1, message about installation +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "shfmt_binary_missing" + +HOOK_PATH="$(cd "$SCRIPT_DIR/../../../../shell" && pwd)/check_shfmt.sh" + +create_fixture "scripts/deploy.sh" "#!/bin/bash +echo hello" + +SAVED_PATH="$PATH" +export PATH="/usr/bin:/bin" + +set +e +# shellcheck disable=SC2034 +LAST_OUTPUT=$("$HOOK_PATH" "scripts/deploy.sh" 2>&1) +# shellcheck disable=SC2034 +LAST_EXIT_CODE=$? +set -e + +export PATH="$SAVED_PATH" + +assert_exit_code 1 +assert_output_contains "shfmt is not installed" + +test_passed diff --git a/tests/shell/shfmt/cases/03_correctly_formatted_passes.sh b/tests/shell/shfmt/cases/03_correctly_formatted_passes.sh new file mode 100755 index 0000000..81db034 --- /dev/null +++ b/tests/shell/shfmt/cases/03_correctly_formatted_passes.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Correctly formatted shell script passes shfmt check +# Expected: exit 0, success message +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "shfmt_correctly_formatted" + +# Mock shfmt to succeed with no diff output +mkdir -p "$TEST_DIR/bin" +cat > "$TEST_DIR/bin/shfmt" << 'EOF' +#!/bin/bash +exit 0 +EOF +chmod +x "$TEST_DIR/bin/shfmt" +export PATH="$TEST_DIR/bin:$PATH" + +create_fixture "scripts/clean.sh" "#!/bin/bash +echo hello" + +run_hook "shell/check_shfmt.sh" "scripts/clean.sh" + +assert_exit_code 0 +assert_output_contains "shfmt check passed" + +test_passed diff --git a/tests/shell/shfmt/cases/04_formatting_issues_fails.sh b/tests/shell/shfmt/cases/04_formatting_issues_fails.sh new file mode 100755 index 0000000..994e541 --- /dev/null +++ b/tests/shell/shfmt/cases/04_formatting_issues_fails.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Shell script with formatting issues fails shfmt check +# Expected: exit 1, error message about shfmt check and diff output +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "shfmt_formatting_issues" + +# Mock shfmt to fail with a diff +mkdir -p "$TEST_DIR/bin" +cat > "$TEST_DIR/bin/shfmt" << 'EOF' +#!/bin/bash +echo "--- scripts/bad.sh" +echo "+++ scripts/bad.sh" +echo "@@ -1,3 +1,3 @@" +echo "- echo hello" +echo "+ echo hello" +exit 1 +EOF +chmod +x "$TEST_DIR/bin/shfmt" +export PATH="$TEST_DIR/bin:$PATH" + +create_fixture "scripts/bad.sh" "#!/bin/bash + echo hello" + +run_hook "shell/check_shfmt.sh" "scripts/bad.sh" + +assert_exit_code 1 +assert_output_contains "ERROR: shfmt check failed" + +test_passed diff --git a/tests/shell/shfmt/run.sh b/tests/shell/shfmt/run.sh new file mode 100755 index 0000000..69721dc --- /dev/null +++ b/tests/shell/shfmt/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Runs all shfmt hook test cases. +# Executes each case script in the cases/ directory. +# ------------------------------------------------------------------------------ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PASSED=0 +FAILED=0 + +for case_file in "$SCRIPT_DIR"/cases/*.sh; do + [[ -f "$case_file" ]] || continue + + if "$case_file"; then + ((PASSED++)) || true + else + ((FAILED++)) || true + fi +done + +echo "" +echo "Shfmt tests: $PASSED passed, $FAILED failed" + +[[ $FAILED -eq 0 ]] diff --git a/tests/simple/codespell/cases/01_no_files_exits_zero.sh b/tests/simple/codespell/cases/01_no_files_exits_zero.sh new file mode 100755 index 0000000..312e360 --- /dev/null +++ b/tests/simple/codespell/cases/01_no_files_exits_zero.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: No files provided exits with 0 +# Expected: exit 0, message about no files +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "codespell_no_files" + +run_hook "simple/check_codespell.sh" + +assert_exit_code 0 +assert_output_contains "No files to check" + +test_passed diff --git a/tests/simple/codespell/cases/02_binary_missing_exits_one.sh b/tests/simple/codespell/cases/02_binary_missing_exits_one.sh new file mode 100755 index 0000000..00087a5 --- /dev/null +++ b/tests/simple/codespell/cases/02_binary_missing_exits_one.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: codespell binary missing exits with 1 and install hint +# Expected: exit 1, message about installation +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "codespell_binary_missing" + +HOOK_PATH="$(cd "$SCRIPT_DIR/../../../../simple" && pwd)/check_codespell.sh" + +create_fixture "app/readme.txt" "This is a tset file." + +SAVED_PATH="$PATH" +export PATH="/usr/bin:/bin" + +set +e +# shellcheck disable=SC2034 +LAST_OUTPUT=$("$HOOK_PATH" "app/readme.txt" 2>&1) +# shellcheck disable=SC2034 +LAST_EXIT_CODE=$? +set -e + +export PATH="$SAVED_PATH" + +assert_exit_code 1 +assert_output_contains "codespell is not installed" + +test_passed diff --git a/tests/simple/codespell/cases/03_clean_file_passes.sh b/tests/simple/codespell/cases/03_clean_file_passes.sh new file mode 100755 index 0000000..f23273c --- /dev/null +++ b/tests/simple/codespell/cases/03_clean_file_passes.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Clean file with no spelling errors passes +# Expected: exit 0, success message +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "codespell_clean_file" + +# Mock codespell to succeed +mkdir -p "$TEST_DIR/bin" +cat > "$TEST_DIR/bin/codespell" << 'EOF' +#!/bin/bash +exit 0 +EOF +chmod +x "$TEST_DIR/bin/codespell" +export PATH="$TEST_DIR/bin:$PATH" + +create_fixture "app/clean.txt" "This is a clean file with correct spelling." + +run_hook "simple/check_codespell.sh" "app/clean.txt" + +assert_exit_code 0 +assert_output_contains "Spelling check passed" + +test_passed diff --git a/tests/simple/codespell/cases/04_file_with_errors_fails.sh b/tests/simple/codespell/cases/04_file_with_errors_fails.sh new file mode 100755 index 0000000..b1e0106 --- /dev/null +++ b/tests/simple/codespell/cases/04_file_with_errors_fails.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: File with spelling errors fails +# Expected: exit 1, error message about spelling check +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "codespell_file_with_errors" + +# Mock codespell to fail with spelling errors +mkdir -p "$TEST_DIR/bin" +cat > "$TEST_DIR/bin/codespell" << 'EOF' +#!/bin/bash +echo "app/bad.txt:1: tset ==> test" +exit 1 +EOF +chmod +x "$TEST_DIR/bin/codespell" +export PATH="$TEST_DIR/bin:$PATH" + +create_fixture "app/bad.txt" "This is a tset file." + +run_hook "simple/check_codespell.sh" "app/bad.txt" + +assert_exit_code 1 +assert_output_contains "ERROR: Spelling check failed" + +test_passed diff --git a/tests/simple/codespell/run.sh b/tests/simple/codespell/run.sh new file mode 100755 index 0000000..0f3daa0 --- /dev/null +++ b/tests/simple/codespell/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Runs all codespell hook test cases. +# Executes each case script in the cases/ directory. +# ------------------------------------------------------------------------------ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PASSED=0 +FAILED=0 + +for case_file in "$SCRIPT_DIR"/cases/*.sh; do + [[ -f "$case_file" ]] || continue + + if "$case_file"; then + ((PASSED++)) || true + else + ((FAILED++)) || true + fi +done + +echo "" +echo "Codespell tests: $PASSED passed, $FAILED failed" + +[[ $FAILED -eq 0 ]] diff --git a/tests/simple/json_validate/cases/01_no_files_exits_zero.sh b/tests/simple/json_validate/cases/01_no_files_exits_zero.sh new file mode 100755 index 0000000..b9102f1 --- /dev/null +++ b/tests/simple/json_validate/cases/01_no_files_exits_zero.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: No files provided exits with 0 +# Expected: exit 0, message about no files +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "json_validate_no_files" + +run_hook "simple/check_json_validate.sh" + +assert_exit_code 0 +assert_output_contains "No files to check" + +test_passed diff --git a/tests/simple/json_validate/cases/02_non_json_files_ignored.sh b/tests/simple/json_validate/cases/02_non_json_files_ignored.sh new file mode 100755 index 0000000..52e0a09 --- /dev/null +++ b/tests/simple/json_validate/cases/02_non_json_files_ignored.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Non-JSON files are ignored +# Expected: exit 0, message about no JSON files +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "json_validate_non_json_ignored" + +create_fixture "app/config.yml" "key: value" + +run_hook "simple/check_json_validate.sh" "app/config.yml" + +assert_exit_code 0 +assert_output_contains "No JSON files to check" + +test_passed diff --git a/tests/simple/json_validate/cases/03_valid_json_passes.sh b/tests/simple/json_validate/cases/03_valid_json_passes.sh new file mode 100755 index 0000000..431d6a4 --- /dev/null +++ b/tests/simple/json_validate/cases/03_valid_json_passes.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Valid JSON file passes validation +# Expected: exit 0, success message +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "json_validate_valid_json" + +create_fixture "app/config.json" '{"name": "test", "version": "1.0.0"}' + +run_hook "simple/check_json_validate.sh" "app/config.json" + +assert_exit_code 0 +assert_output_contains "JSON validation passed" + +test_passed diff --git a/tests/simple/json_validate/cases/04_invalid_json_fails.sh b/tests/simple/json_validate/cases/04_invalid_json_fails.sh new file mode 100755 index 0000000..e5e45a5 --- /dev/null +++ b/tests/simple/json_validate/cases/04_invalid_json_fails.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Invalid JSON file fails validation +# Expected: exit 1, error message about JSON validation +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "json_validate_invalid_json" + +create_fixture "app/bad.json" '{invalid json: missing quotes}' + +run_hook "simple/check_json_validate.sh" "app/bad.json" + +assert_exit_code 1 +assert_output_contains "ERROR: JSON validation failed" + +test_passed diff --git a/tests/simple/json_validate/run.sh b/tests/simple/json_validate/run.sh new file mode 100755 index 0000000..e7de957 --- /dev/null +++ b/tests/simple/json_validate/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Runs all json_validate hook test cases. +# Executes each case script in the cases/ directory. +# ------------------------------------------------------------------------------ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PASSED=0 +FAILED=0 + +for case_file in "$SCRIPT_DIR"/cases/*.sh; do + [[ -f "$case_file" ]] || continue + + if "$case_file"; then + ((PASSED++)) || true + else + ((FAILED++)) || true + fi +done + +echo "" +echo "JSON validate tests: $PASSED passed, $FAILED failed" + +[[ $FAILED -eq 0 ]]