From f6bd6ef71af55566453ee55c3763b40668f0013e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:43:12 +0300 Subject: [PATCH 01/17] create find_test.sh --- python/find_test.sh | 135 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 python/find_test.sh diff --git a/python/find_test.sh b/python/find_test.sh new file mode 100644 index 0000000..44c5406 --- /dev/null +++ b/python/find_test.sh @@ -0,0 +1,135 @@ +#!/bin/bash + +# ----------------------------- +# FUNCTIONS +# ----------------------------- +# Rules: +# - check only services: app/services/**/*.py +# - 1 service = 1 test file +# - mirror path relative to app/: app/.../x.py -> tests/.../test_x.py +get_test_path() { + local file="$1" + + # Skip files already under tests/ + if [[ "$file" == tests/* ]]; then + echo "" + return + fi + + # Ensure it's a service file + if [[ ! "$file" =~ ^app/services/.*\.py$ ]]; then + echo "" + return + fi + + # Skip __init__.py + local filename + filename=$(basename "$file") + if [[ "$filename" == "__init__.py" ]]; then + echo "" + return + fi + + # Remove app/ prefix and mirror into tests/ + local relative="${file#app/}" # services/... + local dir + dir=$(dirname "$relative") # services[/...] + + local test_filename="test_${filename}" + local test_path="tests/${dir}/${test_filename}" + + echo "$test_path" +} + +# ----------------------------- +# MAIN +# ----------------------------- +if [ $# -eq 0 ]; then + echo "No files to check" + exit 0 +fi + +MISSING_TESTS=() +DUPLICATE_TESTS=() +CHECKED_FILES=0 + +for file in "$@"; do + # Skip non-Python files + if [[ ! "$file" =~ \.py$ ]]; then + continue + fi + + # Skip files under tests/ + if [[ "$file" == tests/* ]]; then + continue + fi + + # Compute expected test path + test_path=$(get_test_path "$file") + + if [ -z "$test_path" ]; then + continue + fi + + CHECKED_FILES=$((CHECKED_FILES + 1)) + + # Check that the expected test exists + if [ ! -f "$test_path" ]; then + MISSING_TESTS+=("$file -> $test_path") + fi + + # Check duplicates: by "1 service = 1 test file" rule only test_.py is allowed + # Any other test_* .py in the same dir is considered a duplicate. + service_filename=$(basename "$file") + service_stem="${service_filename%.py}" + test_dir=$(dirname "$test_path") + expected_test="$test_path" + + shopt -s nullglob + candidates=("${test_dir}/test_${service_stem}"*.py) + shopt -u nullglob + + duplicates=() + for candidate in "${candidates[@]}"; do + if [ "$candidate" != "$expected_test" ]; then + duplicates+=("$candidate") + fi + done + + if [ ${#duplicates[@]} -gt 0 ]; then + DUPLICATE_TESTS+=("$file -> extra tests found: ${duplicates[*]}") + fi +done + +# Report results +if [ ${#MISSING_TESTS[@]} -gt 0 ]; then + echo "ERROR: Missing tests for the following services:" + echo "" + for missing in "${MISSING_TESTS[@]}"; do + echo " - $missing" + done + echo "" + echo "Total files checked: $CHECKED_FILES" + echo "Missing tests: ${#MISSING_TESTS[@]}" + exit 1 +fi + +if [ ${#DUPLICATE_TESTS[@]} -gt 0 ]; then + echo "ERROR: '1 service = 1 test file' rule violated (duplicates found):" + echo "" + for dup in "${DUPLICATE_TESTS[@]}"; do + echo " - $dup" + done + echo "" + echo "Total files checked: $CHECKED_FILES" + echo "Duplicate groups: ${#DUPLICATE_TESTS[@]}" + exit 1 +fi + +if [ $CHECKED_FILES -eq 0 ]; then + echo "No service files to check (skipping)." +else + echo "All checks passed! ($CHECKED_FILES files checked)" +fi + +exit 0 \ No newline at end of file From 57e844dbddaa2ae3d195cf4ddd3c244fe4dc819a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:45:18 +0300 Subject: [PATCH 02/17] fixed find_test.sh --- python/find_test.sh | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/python/find_test.sh b/python/find_test.sh index 44c5406..e569b95 100644 --- a/python/find_test.sh +++ b/python/find_test.sh @@ -1,12 +1,15 @@ #!/bin/bash - -# ----------------------------- -# FUNCTIONS -# ----------------------------- +# ------------------------------------------------------------------ +# Checks test files for services in app/services. # Rules: -# - check only services: app/services/**/*.py -# - 1 service = 1 test file -# - mirror path relative to app/: app/.../x.py -> tests/.../test_x.py +# - Only checks files in app/services/**/*.py +# - Each service must have exactly one test file +# - Test file mirrors the path relative to app/: +# app/services/.../x.py -> tests/services/.../test_x.py +# Usage: +# ./check_service_tests.sh ... +# ------------------------------------------------------------------ + get_test_path() { local file="$1" @@ -30,20 +33,15 @@ get_test_path() { return fi - # Remove app/ prefix and mirror into tests/ - local relative="${file#app/}" # services/... + local relative="${file#app/}" local dir - dir=$(dirname "$relative") # services[/...] - + dir=$(dirname "$relative") local test_filename="test_${filename}" local test_path="tests/${dir}/${test_filename}" echo "$test_path" } -# ----------------------------- -# MAIN -# ----------------------------- if [ $# -eq 0 ]; then echo "No files to check" exit 0 @@ -59,12 +57,6 @@ for file in "$@"; do continue fi - # Skip files under tests/ - if [[ "$file" == tests/* ]]; then - continue - fi - - # Compute expected test path test_path=$(get_test_path "$file") if [ -z "$test_path" ]; then @@ -104,11 +96,9 @@ done # Report results if [ ${#MISSING_TESTS[@]} -gt 0 ]; then echo "ERROR: Missing tests for the following services:" - echo "" for missing in "${MISSING_TESTS[@]}"; do echo " - $missing" done - echo "" echo "Total files checked: $CHECKED_FILES" echo "Missing tests: ${#MISSING_TESTS[@]}" exit 1 @@ -116,11 +106,9 @@ fi if [ ${#DUPLICATE_TESTS[@]} -gt 0 ]; then echo "ERROR: '1 service = 1 test file' rule violated (duplicates found):" - echo "" for dup in "${DUPLICATE_TESTS[@]}"; do echo " - $dup" done - echo "" echo "Total files checked: $CHECKED_FILES" echo "Duplicate groups: ${#DUPLICATE_TESTS[@]}" exit 1 @@ -132,4 +120,4 @@ else echo "All checks passed! ($CHECKED_FILES files checked)" fi -exit 0 \ No newline at end of file +exit 0 From dfdf726cbea1b20fe0a54805f19fd6b39a91e86a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:46:58 +0300 Subject: [PATCH 03/17] create check_flake8.sh --- python/check_flake8.sh | 101 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 python/check_flake8.sh diff --git a/python/check_flake8.sh b/python/check_flake8.sh new file mode 100644 index 0000000..530d041 --- /dev/null +++ b/python/check_flake8.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +# ----------------------------- +# CONFIG +# ----------------------------- +CONTAINER_NAME="fastapi_app_dev" +LINTER_CMD="flake8" +LINTER_OPTIONS="--max-line-length=120" + +# Path mapping: host path prefix -> container path prefix +HOST_APP_PATH="app/" +CONTAINER_APP_PATH="/app/" + +# ----------------------------- +# FUNCTIONS +# ----------------------------- +check_container_running() { + if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + echo "ERROR: Container '$CONTAINER_NAME' is not running" + echo "Start it with: docker-compose -f docker/dev/docker-compose.yml up -d" + exit 1 + fi +} + +# Convert host path to container path +to_container_path() { + local file="$1" + # Replace app/ with /app/ + echo "$file" | sed "s|^${HOST_APP_PATH}|${CONTAINER_APP_PATH}|" +} + +# ----------------------------- +# MAIN +# ----------------------------- +if [ $# -eq 0 ]; then + echo "No files to check" + exit 0 +fi + +check_container_running + +PY_FILES=() +CHECKED_FILES=0 +HAS_ERRORS=0 + +# Collect only .py files from app/ directory +for file in "$@"; do + # Skip non-Python files + if [[ ! "$file" =~ \.py$ ]]; then + continue + fi + + # Skip files not in app/ directory (not mounted in container) + if [[ ! "$file" =~ ^app/ ]]; then + continue + fi + + # Skip if file doesn't exist + if [ ! -f "$file" ]; then + continue + fi + + PY_FILES+=("$file") +done + +# Check if there are any Python files to check +if [ ${#PY_FILES[@]} -eq 0 ]; then + echo "No Python files to check" + exit 0 +fi + +# Run linter on each file via docker exec +for file in "${PY_FILES[@]}"; do + CHECKED_FILES=$((CHECKED_FILES + 1)) + + container_path=$(to_container_path "$file") + + # Run linter inside container + OUTPUT=$(docker exec "$CONTAINER_NAME" $LINTER_CMD $LINTER_OPTIONS "$container_path" 2>&1) + EXIT_CODE=$? + + if [ $EXIT_CODE -ne 0 ]; then + HAS_ERRORS=1 + echo "Style errors in: $file" + # Convert container paths back to host paths in output + echo "$OUTPUT" | sed "s|${CONTAINER_APP_PATH}|${HOST_APP_PATH}|g" + echo "" + fi +done + +# Final result +if [ $HAS_ERRORS -ne 0 ]; then + echo "----------------------------------------" + echo "ERROR: Code style check failed!" + echo "Total files checked: $CHECKED_FILES" + echo "Fix the errors above before committing." + exit 1 +fi + +echo "Code style check passed! ($CHECKED_FILES files checked)" +exit 0 From f36861704ea449b2ab43fb94d9cade278caa170d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:48:31 +0300 Subject: [PATCH 04/17] fixed check_flake8.sh --- python/check_flake8.sh | 60 +++++++++--------------------------------- 1 file changed, 13 insertions(+), 47 deletions(-) diff --git a/python/check_flake8.sh b/python/check_flake8.sh index 530d041..7d63bd8 100644 --- a/python/check_flake8.sh +++ b/python/check_flake8.sh @@ -1,60 +1,30 @@ #!/bin/bash +# ---------------------------------------- +# Python Code Style Checker +# +# This script checks Python files for style +# issues using flake8. It runs directly on +# the host (no Docker required). +# +# Usage: +# ./check_style.sh file1.py file2.py ... +# ---------------------------------------- -# ----------------------------- -# CONFIG -# ----------------------------- -CONTAINER_NAME="fastapi_app_dev" -LINTER_CMD="flake8" -LINTER_OPTIONS="--max-line-length=120" - -# Path mapping: host path prefix -> container path prefix -HOST_APP_PATH="app/" -CONTAINER_APP_PATH="/app/" - -# ----------------------------- -# FUNCTIONS -# ----------------------------- -check_container_running() { - if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then - echo "ERROR: Container '$CONTAINER_NAME' is not running" - echo "Start it with: docker-compose -f docker/dev/docker-compose.yml up -d" - exit 1 - fi -} - -# Convert host path to container path -to_container_path() { - local file="$1" - # Replace app/ with /app/ - echo "$file" | sed "s|^${HOST_APP_PATH}|${CONTAINER_APP_PATH}|" -} - -# ----------------------------- -# MAIN -# ----------------------------- if [ $# -eq 0 ]; then echo "No files to check" exit 0 fi -check_container_running - PY_FILES=() CHECKED_FILES=0 HAS_ERRORS=0 -# Collect only .py files from app/ directory for file in "$@"; do # Skip non-Python files if [[ ! "$file" =~ \.py$ ]]; then continue fi - # Skip files not in app/ directory (not mounted in container) - if [[ ! "$file" =~ ^app/ ]]; then - continue - fi - # Skip if file doesn't exist if [ ! -f "$file" ]; then continue @@ -69,21 +39,17 @@ if [ ${#PY_FILES[@]} -eq 0 ]; then exit 0 fi -# Run linter on each file via docker exec +# Run flake8 linter on each file for file in "${PY_FILES[@]}"; do CHECKED_FILES=$((CHECKED_FILES + 1)) - container_path=$(to_container_path "$file") - - # Run linter inside container - OUTPUT=$(docker exec "$CONTAINER_NAME" $LINTER_CMD $LINTER_OPTIONS "$container_path" 2>&1) + OUTPUT=$(flake8 --max-line-length=120 "$file" 2>&1) EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then HAS_ERRORS=1 echo "Style errors in: $file" - # Convert container paths back to host paths in output - echo "$OUTPUT" | sed "s|${CONTAINER_APP_PATH}|${HOST_APP_PATH}|g" + echo "$OUTPUT" echo "" fi done From d88b2564437a2b91dc6ecb903d2376d56982ba50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:49:19 +0300 Subject: [PATCH 05/17] created check_flake8_in_docker.sh --- python/check_flake8_in_docker.sh | 101 +++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 python/check_flake8_in_docker.sh diff --git a/python/check_flake8_in_docker.sh b/python/check_flake8_in_docker.sh new file mode 100644 index 0000000..530d041 --- /dev/null +++ b/python/check_flake8_in_docker.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +# ----------------------------- +# CONFIG +# ----------------------------- +CONTAINER_NAME="fastapi_app_dev" +LINTER_CMD="flake8" +LINTER_OPTIONS="--max-line-length=120" + +# Path mapping: host path prefix -> container path prefix +HOST_APP_PATH="app/" +CONTAINER_APP_PATH="/app/" + +# ----------------------------- +# FUNCTIONS +# ----------------------------- +check_container_running() { + if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + echo "ERROR: Container '$CONTAINER_NAME' is not running" + echo "Start it with: docker-compose -f docker/dev/docker-compose.yml up -d" + exit 1 + fi +} + +# Convert host path to container path +to_container_path() { + local file="$1" + # Replace app/ with /app/ + echo "$file" | sed "s|^${HOST_APP_PATH}|${CONTAINER_APP_PATH}|" +} + +# ----------------------------- +# MAIN +# ----------------------------- +if [ $# -eq 0 ]; then + echo "No files to check" + exit 0 +fi + +check_container_running + +PY_FILES=() +CHECKED_FILES=0 +HAS_ERRORS=0 + +# Collect only .py files from app/ directory +for file in "$@"; do + # Skip non-Python files + if [[ ! "$file" =~ \.py$ ]]; then + continue + fi + + # Skip files not in app/ directory (not mounted in container) + if [[ ! "$file" =~ ^app/ ]]; then + continue + fi + + # Skip if file doesn't exist + if [ ! -f "$file" ]; then + continue + fi + + PY_FILES+=("$file") +done + +# Check if there are any Python files to check +if [ ${#PY_FILES[@]} -eq 0 ]; then + echo "No Python files to check" + exit 0 +fi + +# Run linter on each file via docker exec +for file in "${PY_FILES[@]}"; do + CHECKED_FILES=$((CHECKED_FILES + 1)) + + container_path=$(to_container_path "$file") + + # Run linter inside container + OUTPUT=$(docker exec "$CONTAINER_NAME" $LINTER_CMD $LINTER_OPTIONS "$container_path" 2>&1) + EXIT_CODE=$? + + if [ $EXIT_CODE -ne 0 ]; then + HAS_ERRORS=1 + echo "Style errors in: $file" + # Convert container paths back to host paths in output + echo "$OUTPUT" | sed "s|${CONTAINER_APP_PATH}|${HOST_APP_PATH}|g" + echo "" + fi +done + +# Final result +if [ $HAS_ERRORS -ne 0 ]; then + echo "----------------------------------------" + echo "ERROR: Code style check failed!" + echo "Total files checked: $CHECKED_FILES" + echo "Fix the errors above before committing." + exit 1 +fi + +echo "Code style check passed! ($CHECKED_FILES files checked)" +exit 0 From 8e1e0778c9aeb095db80dfa614d93b2f2dfc13aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:51:15 +0300 Subject: [PATCH 06/17] fixed check_flake8_in_docker.sh --- python/check_flake8_in_docker.sh | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/python/check_flake8_in_docker.sh b/python/check_flake8_in_docker.sh index 530d041..52be024 100644 --- a/python/check_flake8_in_docker.sh +++ b/python/check_flake8_in_docker.sh @@ -1,11 +1,19 @@ #!/bin/bash +# ---------------------------------------- +# Python Code Style Checker +# +# This script checks Python files for style +# issues using flake8. It runs directly on +# the host (no Docker required). +# +# Usage: +# ./check_style.sh file1.py file2.py ... +# ---------------------------------------- # ----------------------------- # CONFIG # ----------------------------- -CONTAINER_NAME="fastapi_app_dev" -LINTER_CMD="flake8" -LINTER_OPTIONS="--max-line-length=120" +CONTAINER_NAME="app_dev" # Path mapping: host path prefix -> container path prefix HOST_APP_PATH="app/" @@ -76,7 +84,7 @@ for file in "${PY_FILES[@]}"; do container_path=$(to_container_path "$file") # Run linter inside container - OUTPUT=$(docker exec "$CONTAINER_NAME" $LINTER_CMD $LINTER_OPTIONS "$container_path" 2>&1) + OUTPUT=$(docker exec "$CONTAINER_NAME" flake8 --max-line-length=120 "$container_path" 2>&1) EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then From af8695fff2ccded26b041860f6e1e1734a1d2878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 16:46:08 +0300 Subject: [PATCH 07/17] create check_mypy_in_docker.sh --- python/check_mypy_in_docker.sh | 90 ++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 python/check_mypy_in_docker.sh diff --git a/python/check_mypy_in_docker.sh b/python/check_mypy_in_docker.sh new file mode 100644 index 0000000..17de758 --- /dev/null +++ b/python/check_mypy_in_docker.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +# ----------------------------- +# CONFIG +# ----------------------------- +CONTAINER_NAME="fastapi_app_dev" + +# Path mapping: host path prefix -> container path prefix +HOST_APP_PATH="app/" +CONTAINER_APP_PATH="/app/" + +# ----------------------------- +# FUNCTIONS +# ----------------------------- +check_container_running() { + if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + echo "ERROR: Container '${CONTAINER_NAME}' is not running" + echo "Start it with: docker-compose -f docker/dev/docker-compose.yml up -d" + exit 1 + fi +} + +# Convert host path to container path +to_container_path() { + local file="$1" + echo "$file" | sed "s|^${HOST_APP_PATH}|${CONTAINER_APP_PATH}|" +} + +# ----------------------------- +# MAIN +# ----------------------------- +if [ $# -eq 0 ]; then + echo "No files to check" + exit 0 +fi + +check_container_running + +PY_FILES=() + +# Collect only .py files from app/ directory +for file in "$@"; do + # Skip non-Python files + if [[ ! "$file" =~ \.py$ ]]; then + continue + fi + + # Skip files not in app/ directory (not mounted in container) + if [[ ! "$file" =~ ^app/ ]]; then + continue + fi + + # Skip if file doesn't exist + if [ ! -f "$file" ]; then + continue + fi + + PY_FILES+=("$file") +done + +# Check if there are any Python files to analyze +if [ ${#PY_FILES[@]} -eq 0 ]; then + echo "No Python files to check" + exit 0 +fi + +# Convert to container paths +CONTAINER_FILES=() +for file in "${PY_FILES[@]}"; do + CONTAINER_FILES+=("$(to_container_path "$file")") +done + +# Run analyzer inside container +OUTPUT=$(docker exec "$CONTAINER_NAME" mypy --pretty --show-error-codes --ignore-missing-imports --follow-imports=skip "${CONTAINER_FILES[@]}" 2>&1) +EXIT_CODE=$? + +if [ $EXIT_CODE -ne 0 ]; then + echo "----------------------------------------" + echo "ERROR: Static analysis failed!" + echo "----------------------------------------" + # Convert container paths back to host paths in output + echo "$OUTPUT" | sed "s|${CONTAINER_APP_PATH}|${HOST_APP_PATH}|g" + echo "----------------------------------------" + echo "Total files checked: ${#PY_FILES[@]}" + exit 1 +fi + +echo "Static analysis passed! (${#PY_FILES[@]} files checked)" +exit 0 + From 93e51bcb7fff26df2694ad102aa94b177c744fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:07:32 +0300 Subject: [PATCH 08/17] create check_mypy_in_docker.sh --- python/check_mypy_in_docker.sh | 35 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/python/check_mypy_in_docker.sh b/python/check_mypy_in_docker.sh index 17de758..37cd7cc 100644 --- a/python/check_mypy_in_docker.sh +++ b/python/check_mypy_in_docker.sh @@ -1,17 +1,16 @@ #!/bin/bash -# ----------------------------- -# CONFIG -# ----------------------------- -CONTAINER_NAME="fastapi_app_dev" - -# Path mapping: host path prefix -> container path prefix +# ------------------------------------------------------------ +# Runs mypy static analysis for changed Python files inside +# the running Docker container. Accepts file paths as args, +# filters only app/*.py, maps them to container paths, and +# executes mypy via docker exec. +# ------------------------------------------------------------ + +CONTAINER_NAME="app_dev" HOST_APP_PATH="app/" CONTAINER_APP_PATH="/app/" -# ----------------------------- -# FUNCTIONS -# ----------------------------- check_container_running() { if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then echo "ERROR: Container '${CONTAINER_NAME}' is not running" @@ -20,15 +19,11 @@ check_container_running() { fi } -# Convert host path to container path to_container_path() { local file="$1" echo "$file" | sed "s|^${HOST_APP_PATH}|${CONTAINER_APP_PATH}|" } -# ----------------------------- -# MAIN -# ----------------------------- if [ $# -eq 0 ]; then echo "No files to check" exit 0 @@ -38,7 +33,6 @@ check_container_running PY_FILES=() -# Collect only .py files from app/ directory for file in "$@"; do # Skip non-Python files if [[ ! "$file" =~ \.py$ ]]; then @@ -58,27 +52,29 @@ for file in "$@"; do PY_FILES+=("$file") done -# Check if there are any Python files to analyze if [ ${#PY_FILES[@]} -eq 0 ]; then echo "No Python files to check" exit 0 fi -# Convert to container paths CONTAINER_FILES=() for file in "${PY_FILES[@]}"; do CONTAINER_FILES+=("$(to_container_path "$file")") done -# Run analyzer inside container -OUTPUT=$(docker exec "$CONTAINER_NAME" mypy --pretty --show-error-codes --ignore-missing-imports --follow-imports=skip "${CONTAINER_FILES[@]}" 2>&1) +OUTPUT=$(docker exec "$CONTAINER_NAME" mypy \ + --pretty \ + --show-error-codes \ + --ignore-missing-imports \ + --follow-imports=skip \ + "${CONTAINER_FILES[@]}" 2>&1) + EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then echo "----------------------------------------" echo "ERROR: Static analysis failed!" echo "----------------------------------------" - # Convert container paths back to host paths in output echo "$OUTPUT" | sed "s|${CONTAINER_APP_PATH}|${HOST_APP_PATH}|g" echo "----------------------------------------" echo "Total files checked: ${#PY_FILES[@]}" @@ -87,4 +83,3 @@ fi echo "Static analysis passed! (${#PY_FILES[@]} files checked)" exit 0 - From 1fc62ea0dfc51d73aea4c59269b5d5604042140e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:10:01 +0300 Subject: [PATCH 09/17] create check_mypy.sh --- python/check_mypy.sh | 59 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 python/check_mypy.sh diff --git a/python/check_mypy.sh b/python/check_mypy.sh new file mode 100644 index 0000000..2bc9645 --- /dev/null +++ b/python/check_mypy.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +# ------------------------------------------------------------ +# Runs mypy static analysis for changed Python files locally. +# Accepts file paths as args and checks only app/*.py files. +# ------------------------------------------------------------ + +if [ $# -eq 0 ]; then + echo "No files to check" + exit 0 +fi + +PY_FILES=() + +for file in "$@"; do + # Skip non-Python files + if [[ ! "$file" =~ \.py$ ]]; then + continue + fi + + # Only files inside app/ + if [[ ! "$file" =~ ^app/ ]]; then + continue + fi + + # Skip if file doesn't exist + 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 + +OUTPUT=$(mypy \ + --pretty \ + --show-error-codes \ + --ignore-missing-imports \ + --follow-imports=skip \ + "${PY_FILES[@]}" 2>&1) + +EXIT_CODE=$? + +if [ $EXIT_CODE -ne 0 ]; then + echo "----------------------------------------" + echo "ERROR: Static analysis failed!" + echo "----------------------------------------" + echo "$OUTPUT" + echo "----------------------------------------" + echo "Total files checked: ${#PY_FILES[@]}" + exit 1 +fi + +echo "Static analysis passed! (${#PY_FILES[@]} files checked)" +exit 0 From 35538c6e9861f9f6f8f3b90e2b6242fa5f3c5887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:11:56 +0300 Subject: [PATCH 10/17] create check_pytest.sh --- python/check_pytest.sh | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 python/check_pytest.sh diff --git a/python/check_pytest.sh b/python/check_pytest.sh new file mode 100644 index 0000000..20a2333 --- /dev/null +++ b/python/check_pytest.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# ----------------------------- +# CONFIG +# ----------------------------- +CONTAINER_NAME="app_dev" + +# Path mapping: container path prefix -> host path prefix (for readable output) +CONTAINER_APP_PATH="/app/" +HOST_APP_PATH="app/" +CONTAINER_TESTS_PATH="/tests/" +HOST_TESTS_PATH="tests/" + +# ----------------------------- +# FUNCTIONS +# ----------------------------- +check_container_running() { + if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + echo "ERROR: Container '${CONTAINER_NAME}' is not running" + echo "Start it with: docker-compose -f docker/dev/docker-compose.yml up -d" + exit 1 + fi +} + +# ----------------------------- +# MAIN +# ----------------------------- +check_container_running + +echo "Running full test suite (pytest)..." + +# Run tests inside container. PYTHONPATH=/app so "routes" and "database" resolve to app code. +OUTPUT=$(docker exec -e PYTHONPATH=/app "$CONTAINER_NAME" pytest -q "$CONTAINER_TESTS_PATH" 2>&1) +EXIT_CODE=$? + +if [ $EXIT_CODE -ne 0 ]; then + echo "----------------------------------------" + echo "ERROR: Tests failed!" + echo "----------------------------------------" + # Convert container paths back to host paths in output + echo "$OUTPUT" | sed \ + -e "s|${CONTAINER_APP_PATH}|${HOST_APP_PATH}|g" \ + -e "s|${CONTAINER_TESTS_PATH}|${HOST_TESTS_PATH}|g" + echo "----------------------------------------" + exit 1 +fi + +echo "All tests passed!" +exit 0 From 29118050a30f15bfc370a99d1f80db3aab8686e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:15:35 +0300 Subject: [PATCH 11/17] create check_pytest --- python/check_pytest.sh | 42 ++++++++------------- python/check_pytest_in_docker.sh | 63 ++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 26 deletions(-) create mode 100644 python/check_pytest_in_docker.sh diff --git a/python/check_pytest.sh b/python/check_pytest.sh index 20a2333..5e0e65a 100644 --- a/python/check_pytest.sh +++ b/python/check_pytest.sh @@ -1,46 +1,36 @@ #!/bin/bash +# ---------------------------------------- +# Run pytest locally (without Docker) +# +# This script: +# • executes the full pytest test suite locally +# • sets PYTHONPATH=./app so app modules resolve correctly +# • returns proper exit codes (0/1) for CI/CD pipelines +# +# Usage: +# ./run_tests.sh +# ---------------------------------------- # ----------------------------- # CONFIG # ----------------------------- -CONTAINER_NAME="app_dev" - -# Path mapping: container path prefix -> host path prefix (for readable output) -CONTAINER_APP_PATH="/app/" -HOST_APP_PATH="app/" -CONTAINER_TESTS_PATH="/tests/" -HOST_TESTS_PATH="tests/" - -# ----------------------------- -# FUNCTIONS -# ----------------------------- -check_container_running() { - if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then - echo "ERROR: Container '${CONTAINER_NAME}' is not running" - echo "Start it with: docker-compose -f docker/dev/docker-compose.yml up -d" - exit 1 - fi -} +PYTHONPATH="./app" +TESTS_PATH="tests/" # ----------------------------- # MAIN # ----------------------------- -check_container_running - echo "Running full test suite (pytest)..." -# Run tests inside container. PYTHONPATH=/app so "routes" and "database" resolve to app code. -OUTPUT=$(docker exec -e PYTHONPATH=/app "$CONTAINER_NAME" pytest -q "$CONTAINER_TESTS_PATH" 2>&1) +# Run tests locally with PYTHONPATH set +OUTPUT=$(PYTHONPATH="$PYTHONPATH" pytest -q "$TESTS_PATH" 2>&1) EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then echo "----------------------------------------" echo "ERROR: Tests failed!" echo "----------------------------------------" - # Convert container paths back to host paths in output - echo "$OUTPUT" | sed \ - -e "s|${CONTAINER_APP_PATH}|${HOST_APP_PATH}|g" \ - -e "s|${CONTAINER_TESTS_PATH}|${HOST_TESTS_PATH}|g" + echo "$OUTPUT" echo "----------------------------------------" exit 1 fi diff --git a/python/check_pytest_in_docker.sh b/python/check_pytest_in_docker.sh new file mode 100644 index 0000000..07ada9a --- /dev/null +++ b/python/check_pytest_in_docker.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# ---------------------------------------- +# Run pytest inside Docker container +# +# This script: +# • checks that the development container is running +# • executes the full pytest test suite inside the container +# • sets PYTHONPATH=/app so app modules resolve correctly +# • converts container paths (/app, /tests) to local paths +# (app/, tests/) for readable error output +# • returns proper exit codes (0/1) for CI/CD pipelines +# +# Usage: +# ./run_tests.sh +# ---------------------------------------- + +# ----------------------------- +# CONFIG +# ----------------------------- +CONTAINER_NAME="app_dev" + +# Path mapping: container path prefix -> host path prefix (for readable output) +CONTAINER_APP_PATH="/app/" +HOST_APP_PATH="app/" +CONTAINER_TESTS_PATH="/tests/" +HOST_TESTS_PATH="tests/" + +# ----------------------------- +# FUNCTIONS +# ----------------------------- +check_container_running() { + if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + echo "ERROR: Container '${CONTAINER_NAME}' is not running" + echo "Start it with: docker-compose -f docker/dev/docker-compose.yml up -d" + exit 1 + fi +} + +# ----------------------------- +# MAIN +# ----------------------------- +check_container_running + +echo "Running full test suite (pytest)..." + +# Run tests inside container. PYTHONPATH=/app so "routes" and "database" resolve to app code. +OUTPUT=$(docker exec -e PYTHONPATH=/app "$CONTAINER_NAME" pytest -q "$CONTAINER_TESTS_PATH" 2>&1) +EXIT_CODE=$? + +if [ $EXIT_CODE -ne 0 ]; then + echo "----------------------------------------" + echo "ERROR: Tests failed!" + echo "----------------------------------------" + # Convert container paths back to host paths in output + echo "$OUTPUT" | sed \ + -e "s|${CONTAINER_APP_PATH}|${HOST_APP_PATH}|g" \ + -e "s|${CONTAINER_TESTS_PATH}|${HOST_TESTS_PATH}|g" + echo "----------------------------------------" + exit 1 +fi + +echo "All tests passed!" +exit 0 From 39aa200c08c23c10dbbd9b48181857bcfc9e8ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:52:48 +0300 Subject: [PATCH 12/17] fixed python linting scripts --- python/check_flake8.sh | 0 python/check_flake8_in_docker.sh | 0 python/check_mypy.sh | 0 python/check_mypy_in_docker.sh | 0 python/check_pytest.sh | 0 python/check_pytest_in_docker.sh | 0 python/find_test.sh | 0 .../cases/01_container_not_running.sh | 20 +++++++++++++++++++ 8 files changed, 20 insertions(+) mode change 100644 => 100755 python/check_flake8.sh mode change 100644 => 100755 python/check_flake8_in_docker.sh mode change 100644 => 100755 python/check_mypy.sh mode change 100644 => 100755 python/check_mypy_in_docker.sh mode change 100644 => 100755 python/check_pytest.sh mode change 100644 => 100755 python/check_pytest_in_docker.sh mode change 100644 => 100755 python/find_test.sh create mode 100755 tests/python/pytest_docker/cases/01_container_not_running.sh diff --git a/python/check_flake8.sh b/python/check_flake8.sh old mode 100644 new mode 100755 diff --git a/python/check_flake8_in_docker.sh b/python/check_flake8_in_docker.sh old mode 100644 new mode 100755 diff --git a/python/check_mypy.sh b/python/check_mypy.sh old mode 100644 new mode 100755 diff --git a/python/check_mypy_in_docker.sh b/python/check_mypy_in_docker.sh old mode 100644 new mode 100755 diff --git a/python/check_pytest.sh b/python/check_pytest.sh old mode 100644 new mode 100755 diff --git a/python/check_pytest_in_docker.sh b/python/check_pytest_in_docker.sh old mode 100644 new mode 100755 diff --git a/python/find_test.sh b/python/find_test.sh old mode 100644 new mode 100755 diff --git a/tests/python/pytest_docker/cases/01_container_not_running.sh b/tests/python/pytest_docker/cases/01_container_not_running.sh new file mode 100755 index 0000000..227c314 --- /dev/null +++ b/tests/python/pytest_docker/cases/01_container_not_running.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Container not running exits with 1 +# Expected: exit 1, message about container not running +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "pytest_docker_container_stopped" + +mock_docker_stopped + +run_hook "python/check_pytest_in_docker.sh" + +assert_exit_code 1 +assert_output_contains "Container 'app_dev' is not running" + +test_passed From b1559b6fb537d5610dc2237381297535658ac97c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:53:28 +0300 Subject: [PATCH 13/17] create tests for python scripts --- tests/lib/test_helper.bash | 85 +++++++++++++++++++ .../find_test/cases/01_no_files_exits_zero.sh | 18 ++++ .../cases/02_non_service_file_skipped.sh | 20 +++++ .../cases/03_service_with_test_passes.sh | 21 +++++ .../find_test/cases/04_missing_test_fails.sh | 20 +++++ .../cases/05_duplicate_test_fails.sh | 22 +++++ .../find_test/cases/06_init_py_skipped.sh | 20 +++++ tests/python/find_test/run.sh | 26 ++++++ .../flake8/cases/01_no_files_exits_zero.sh | 20 +++++ .../flake8/cases/02_no_py_files_exits_zero.sh | 22 +++++ .../flake8/cases/03_clean_file_passes.sh | 22 +++++ .../flake8/cases/04_file_with_errors_fails.sh | 22 +++++ .../cases/05_nonexistent_file_skipped.sh | 20 +++++ tests/python/flake8/run.sh | 26 ++++++ .../cases/01_no_files_exits_zero.sh | 20 +++++ .../cases/02_container_not_running.sh | 22 +++++ .../cases/03_clean_file_passes.sh | 22 +++++ tests/python/flake8_docker/run.sh | 26 ++++++ .../mypy/cases/01_no_files_exits_zero.sh | 20 +++++ .../cases/02_no_app_py_files_exits_zero.sh | 22 +++++ .../python/mypy/cases/03_clean_file_passes.sh | 22 +++++ .../mypy/cases/04_file_with_errors_fails.sh | 22 +++++ tests/python/mypy/run.sh | 26 ++++++ .../cases/01_no_files_exits_zero.sh | 20 +++++ .../cases/02_container_not_running.sh | 22 +++++ .../mypy_docker/cases/03_clean_file_passes.sh | 22 +++++ tests/python/mypy_docker/run.sh | 26 ++++++ .../pytest_docker/cases/02_tests_pass.sh | 20 +++++ .../pytest_docker/cases/03_tests_fail.sh | 20 +++++ tests/python/pytest_docker/run.sh | 26 ++++++ .../pytest_local/cases/01_tests_pass.sh | 22 +++++ .../pytest_local/cases/02_tests_fail.sh | 22 +++++ tests/python/pytest_local/run.sh | 26 ++++++ 33 files changed, 792 insertions(+) create mode 100755 tests/python/find_test/cases/01_no_files_exits_zero.sh create mode 100755 tests/python/find_test/cases/02_non_service_file_skipped.sh create mode 100755 tests/python/find_test/cases/03_service_with_test_passes.sh create mode 100755 tests/python/find_test/cases/04_missing_test_fails.sh create mode 100755 tests/python/find_test/cases/05_duplicate_test_fails.sh create mode 100755 tests/python/find_test/cases/06_init_py_skipped.sh create mode 100755 tests/python/find_test/run.sh create mode 100755 tests/python/flake8/cases/01_no_files_exits_zero.sh create mode 100755 tests/python/flake8/cases/02_no_py_files_exits_zero.sh create mode 100755 tests/python/flake8/cases/03_clean_file_passes.sh create mode 100755 tests/python/flake8/cases/04_file_with_errors_fails.sh create mode 100755 tests/python/flake8/cases/05_nonexistent_file_skipped.sh create mode 100755 tests/python/flake8/run.sh create mode 100755 tests/python/flake8_docker/cases/01_no_files_exits_zero.sh create mode 100755 tests/python/flake8_docker/cases/02_container_not_running.sh create mode 100755 tests/python/flake8_docker/cases/03_clean_file_passes.sh create mode 100755 tests/python/flake8_docker/run.sh create mode 100755 tests/python/mypy/cases/01_no_files_exits_zero.sh create mode 100755 tests/python/mypy/cases/02_no_app_py_files_exits_zero.sh create mode 100755 tests/python/mypy/cases/03_clean_file_passes.sh create mode 100755 tests/python/mypy/cases/04_file_with_errors_fails.sh create mode 100755 tests/python/mypy/run.sh create mode 100755 tests/python/mypy_docker/cases/01_no_files_exits_zero.sh create mode 100755 tests/python/mypy_docker/cases/02_container_not_running.sh create mode 100755 tests/python/mypy_docker/cases/03_clean_file_passes.sh create mode 100755 tests/python/mypy_docker/run.sh create mode 100755 tests/python/pytest_docker/cases/02_tests_pass.sh create mode 100755 tests/python/pytest_docker/cases/03_tests_fail.sh create mode 100755 tests/python/pytest_docker/run.sh create mode 100755 tests/python/pytest_local/cases/01_tests_pass.sh create mode 100755 tests/python/pytest_local/cases/02_tests_fail.sh create mode 100755 tests/python/pytest_local/run.sh diff --git a/tests/lib/test_helper.bash b/tests/lib/test_helper.bash index 186cd52..0809022 100755 --- a/tests/lib/test_helper.bash +++ b/tests/lib/test_helper.bash @@ -120,6 +120,91 @@ EOF chmod +x "$TEST_DIR/vendor/bin/pint" } +mock_flake8() { + local exit_code="$1" + local output="${2:-}" + + mkdir -p "$TEST_DIR/bin" + cat > "$TEST_DIR/bin/flake8" << EOF +#!/bin/bash +echo "$output" +exit $exit_code +EOF + chmod +x "$TEST_DIR/bin/flake8" + export PATH="$TEST_DIR/bin:$PATH" +} + +mock_mypy() { + local exit_code="$1" + local output="${2:-}" + + mkdir -p "$TEST_DIR/bin" + cat > "$TEST_DIR/bin/mypy" << EOF +#!/bin/bash +echo "$output" +exit $exit_code +EOF + chmod +x "$TEST_DIR/bin/mypy" + export PATH="$TEST_DIR/bin:$PATH" +} + +mock_pytest_cmd() { + local exit_code="$1" + local output="${2:-}" + + mkdir -p "$TEST_DIR/bin" + cat > "$TEST_DIR/bin/pytest" << EOF +#!/bin/bash +echo "$output" +exit $exit_code +EOF + chmod +x "$TEST_DIR/bin/pytest" + export PATH="$TEST_DIR/bin:$PATH" +} + +mock_docker_running() { + local exec_exit_code="${1:-0}" + local exec_output="${2:-}" + + mkdir -p "$TEST_DIR/bin" + cat > "$TEST_DIR/bin/docker" << EOF +#!/bin/bash +if [[ "\$1" == "ps" ]]; then + echo "app_dev" +elif [[ "\$1" == "exec" ]]; then + echo "$exec_output" + exit $exec_exit_code +fi +exit 0 +EOF + chmod +x "$TEST_DIR/bin/docker" + export PATH="$TEST_DIR/bin:$PATH" +} + +mock_docker_stopped() { + mkdir -p "$TEST_DIR/bin" + cat > "$TEST_DIR/bin/docker" << 'EOF' +#!/bin/bash +if [[ "$1" == "ps" ]]; then + echo "" +fi +exit 0 +EOF + chmod +x "$TEST_DIR/bin/docker" + export PATH="$TEST_DIR/bin:$PATH" +} + +create_python_fixture() { + local path="$1" + local class_name="${2:-MyClass}" + + mkdir -p "$(dirname "$path")" + cat > "$path" << EOF +class $class_name: + pass +EOF +} + mock_git_branch() { local branch_name="$1" diff --git a/tests/python/find_test/cases/01_no_files_exits_zero.sh b/tests/python/find_test/cases/01_no_files_exits_zero.sh new file mode 100755 index 0000000..9193b18 --- /dev/null +++ b/tests/python/find_test/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 "find_test_no_files" + +run_hook "python/find_test.sh" + +assert_exit_code 0 +assert_output_contains "No files to check" + +test_passed diff --git a/tests/python/find_test/cases/02_non_service_file_skipped.sh b/tests/python/find_test/cases/02_non_service_file_skipped.sh new file mode 100755 index 0000000..b9a2603 --- /dev/null +++ b/tests/python/find_test/cases/02_non_service_file_skipped.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Non-service file is skipped +# Expected: exit 0, message about no service files +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "find_test_non_service" + +create_python_fixture "app/models/user.py" "User" + +run_hook "python/find_test.sh" "app/models/user.py" + +assert_exit_code 0 +assert_output_contains "No service files to check" + +test_passed diff --git a/tests/python/find_test/cases/03_service_with_test_passes.sh b/tests/python/find_test/cases/03_service_with_test_passes.sh new file mode 100755 index 0000000..5114dd6 --- /dev/null +++ b/tests/python/find_test/cases/03_service_with_test_passes.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Service file with matching test passes +# Expected: exit 0, message about all checks passed +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "find_test_service_with_test" + +create_python_fixture "app/services/user_service.py" "UserService" +create_python_fixture "tests/services/test_user_service.py" "TestUserService" + +run_hook "python/find_test.sh" "app/services/user_service.py" + +assert_exit_code 0 +assert_output_contains "All checks passed" + +test_passed diff --git a/tests/python/find_test/cases/04_missing_test_fails.sh b/tests/python/find_test/cases/04_missing_test_fails.sh new file mode 100755 index 0000000..a9b511b --- /dev/null +++ b/tests/python/find_test/cases/04_missing_test_fails.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Service file without matching test fails +# Expected: exit 1, message about missing tests +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "find_test_missing_test" + +create_python_fixture "app/services/payment_service.py" "PaymentService" + +run_hook "python/find_test.sh" "app/services/payment_service.py" + +assert_exit_code 1 +assert_output_contains "Missing tests" + +test_passed diff --git a/tests/python/find_test/cases/05_duplicate_test_fails.sh b/tests/python/find_test/cases/05_duplicate_test_fails.sh new file mode 100755 index 0000000..704cbb2 --- /dev/null +++ b/tests/python/find_test/cases/05_duplicate_test_fails.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Duplicate test files violate 1-service-1-test rule +# Expected: exit 1, message about 1 service = 1 test file +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "find_test_duplicate" + +create_python_fixture "app/services/order_service.py" "OrderService" +create_python_fixture "tests/services/test_order_service.py" "TestOrderService" +create_python_fixture "tests/services/test_order_service_v2.py" "TestOrderServiceV2" + +run_hook "python/find_test.sh" "app/services/order_service.py" + +assert_exit_code 1 +assert_output_contains "1 service = 1 test file" + +test_passed diff --git a/tests/python/find_test/cases/06_init_py_skipped.sh b/tests/python/find_test/cases/06_init_py_skipped.sh new file mode 100755 index 0000000..bd61932 --- /dev/null +++ b/tests/python/find_test/cases/06_init_py_skipped.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: __init__.py files are skipped +# Expected: exit 0, message about no service files +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "find_test_init_py" + +create_python_fixture "app/services/__init__.py" "Init" + +run_hook "python/find_test.sh" "app/services/__init__.py" + +assert_exit_code 0 +assert_output_contains "No service files to check" + +test_passed diff --git a/tests/python/find_test/run.sh b/tests/python/find_test/run.sh new file mode 100755 index 0000000..465205a --- /dev/null +++ b/tests/python/find_test/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Runs all find_test 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 "Find test tests: $PASSED passed, $FAILED failed" + +[[ $FAILED -eq 0 ]] diff --git a/tests/python/flake8/cases/01_no_files_exits_zero.sh b/tests/python/flake8/cases/01_no_files_exits_zero.sh new file mode 100755 index 0000000..53e98d9 --- /dev/null +++ b/tests/python/flake8/cases/01_no_files_exits_zero.sh @@ -0,0 +1,20 @@ +#!/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 "flake8_no_files" + +mock_flake8 0 + +run_hook "python/check_flake8.sh" + +assert_exit_code 0 +assert_output_contains "No files to check" + +test_passed diff --git a/tests/python/flake8/cases/02_no_py_files_exits_zero.sh b/tests/python/flake8/cases/02_no_py_files_exits_zero.sh new file mode 100755 index 0000000..c4eab1b --- /dev/null +++ b/tests/python/flake8/cases/02_no_py_files_exits_zero.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: No .py files among arguments exits with 0 +# Expected: exit 0, message about no Python files +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "flake8_no_py_files" + +mock_flake8 0 + +create_fixture "readme.md" "# Readme" + +run_hook "python/check_flake8.sh" "readme.md" + +assert_exit_code 0 +assert_output_contains "No Python files to check" + +test_passed diff --git a/tests/python/flake8/cases/03_clean_file_passes.sh b/tests/python/flake8/cases/03_clean_file_passes.sh new file mode 100755 index 0000000..ad0812a --- /dev/null +++ b/tests/python/flake8/cases/03_clean_file_passes.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Clean file passes flake8 check +# Expected: exit 0, message about check passed +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "flake8_clean_file" + +mock_flake8 0 + +create_python_fixture "app/clean.py" "CleanClass" + +run_hook "python/check_flake8.sh" "app/clean.py" + +assert_exit_code 0 +assert_output_contains "Code style check passed" + +test_passed diff --git a/tests/python/flake8/cases/04_file_with_errors_fails.sh b/tests/python/flake8/cases/04_file_with_errors_fails.sh new file mode 100755 index 0000000..09fc41c --- /dev/null +++ b/tests/python/flake8/cases/04_file_with_errors_fails.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: File with style errors fails +# Expected: exit 1, message about check failed +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "flake8_file_with_errors" + +mock_flake8 1 "app/bad.py:1:1: E302 expected 2 blank lines" + +create_python_fixture "app/bad.py" "BadClass" + +run_hook "python/check_flake8.sh" "app/bad.py" + +assert_exit_code 1 +assert_output_contains "Code style check failed" + +test_passed diff --git a/tests/python/flake8/cases/05_nonexistent_file_skipped.sh b/tests/python/flake8/cases/05_nonexistent_file_skipped.sh new file mode 100755 index 0000000..a00e2c0 --- /dev/null +++ b/tests/python/flake8/cases/05_nonexistent_file_skipped.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Non-existent file is skipped +# Expected: exit 0, message about no Python files +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "flake8_nonexistent_file" + +mock_flake8 0 + +run_hook "python/check_flake8.sh" "nonexistent.py" + +assert_exit_code 0 +assert_output_contains "No Python files to check" + +test_passed diff --git a/tests/python/flake8/run.sh b/tests/python/flake8/run.sh new file mode 100755 index 0000000..9f8cbad --- /dev/null +++ b/tests/python/flake8/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Runs all flake8 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 "Flake8 tests: $PASSED passed, $FAILED failed" + +[[ $FAILED -eq 0 ]] diff --git a/tests/python/flake8_docker/cases/01_no_files_exits_zero.sh b/tests/python/flake8_docker/cases/01_no_files_exits_zero.sh new file mode 100755 index 0000000..7e58433 --- /dev/null +++ b/tests/python/flake8_docker/cases/01_no_files_exits_zero.sh @@ -0,0 +1,20 @@ +#!/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 "flake8_docker_no_files" + +mock_docker_running 0 + +run_hook "python/check_flake8_in_docker.sh" + +assert_exit_code 0 +assert_output_contains "No files to check" + +test_passed diff --git a/tests/python/flake8_docker/cases/02_container_not_running.sh b/tests/python/flake8_docker/cases/02_container_not_running.sh new file mode 100755 index 0000000..2f606ae --- /dev/null +++ b/tests/python/flake8_docker/cases/02_container_not_running.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Container not running exits with 1 +# Expected: exit 1, message about container not running +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "flake8_docker_container_stopped" + +mock_docker_stopped + +create_python_fixture "app/service.py" "Service" + +run_hook "python/check_flake8_in_docker.sh" "app/service.py" + +assert_exit_code 1 +assert_output_contains "Container 'app_dev' is not running" + +test_passed diff --git a/tests/python/flake8_docker/cases/03_clean_file_passes.sh b/tests/python/flake8_docker/cases/03_clean_file_passes.sh new file mode 100755 index 0000000..bf2161f --- /dev/null +++ b/tests/python/flake8_docker/cases/03_clean_file_passes.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Clean file passes in Docker +# Expected: exit 0, message about check passed +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "flake8_docker_clean_file" + +mock_docker_running 0 + +create_python_fixture "app/clean.py" "CleanClass" + +run_hook "python/check_flake8_in_docker.sh" "app/clean.py" + +assert_exit_code 0 +assert_output_contains "Code style check passed" + +test_passed diff --git a/tests/python/flake8_docker/run.sh b/tests/python/flake8_docker/run.sh new file mode 100755 index 0000000..06a0488 --- /dev/null +++ b/tests/python/flake8_docker/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Runs all flake8 Docker 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 "Flake8 Docker tests: $PASSED passed, $FAILED failed" + +[[ $FAILED -eq 0 ]] diff --git a/tests/python/mypy/cases/01_no_files_exits_zero.sh b/tests/python/mypy/cases/01_no_files_exits_zero.sh new file mode 100755 index 0000000..16c0387 --- /dev/null +++ b/tests/python/mypy/cases/01_no_files_exits_zero.sh @@ -0,0 +1,20 @@ +#!/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 "mypy_no_files" + +mock_mypy 0 + +run_hook "python/check_mypy.sh" + +assert_exit_code 0 +assert_output_contains "No files to check" + +test_passed diff --git a/tests/python/mypy/cases/02_no_app_py_files_exits_zero.sh b/tests/python/mypy/cases/02_no_app_py_files_exits_zero.sh new file mode 100755 index 0000000..1a9808e --- /dev/null +++ b/tests/python/mypy/cases/02_no_app_py_files_exits_zero.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: No app/ .py files among arguments exits with 0 +# Expected: exit 0, message about no Python files +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "mypy_no_app_py_files" + +mock_mypy 0 + +create_python_fixture "lib/helper.py" "Helper" + +run_hook "python/check_mypy.sh" "lib/helper.py" + +assert_exit_code 0 +assert_output_contains "No Python files to check" + +test_passed diff --git a/tests/python/mypy/cases/03_clean_file_passes.sh b/tests/python/mypy/cases/03_clean_file_passes.sh new file mode 100755 index 0000000..6a77b8c --- /dev/null +++ b/tests/python/mypy/cases/03_clean_file_passes.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Clean file passes mypy check +# Expected: exit 0, message about analysis passed +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "mypy_clean_file" + +mock_mypy 0 "Success: no issues found" + +create_python_fixture "app/clean.py" "CleanClass" + +run_hook "python/check_mypy.sh" "app/clean.py" + +assert_exit_code 0 +assert_output_contains "Static analysis passed" + +test_passed diff --git a/tests/python/mypy/cases/04_file_with_errors_fails.sh b/tests/python/mypy/cases/04_file_with_errors_fails.sh new file mode 100755 index 0000000..c3b468e --- /dev/null +++ b/tests/python/mypy/cases/04_file_with_errors_fails.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: File with type errors fails +# Expected: exit 1, message about analysis failed +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "mypy_file_with_errors" + +mock_mypy 1 "app/bad.py:1: error: Incompatible return type" + +create_python_fixture "app/bad.py" "BadClass" + +run_hook "python/check_mypy.sh" "app/bad.py" + +assert_exit_code 1 +assert_output_contains "Static analysis failed" + +test_passed diff --git a/tests/python/mypy/run.sh b/tests/python/mypy/run.sh new file mode 100755 index 0000000..a40b598 --- /dev/null +++ b/tests/python/mypy/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Runs all mypy 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 "Mypy tests: $PASSED passed, $FAILED failed" + +[[ $FAILED -eq 0 ]] diff --git a/tests/python/mypy_docker/cases/01_no_files_exits_zero.sh b/tests/python/mypy_docker/cases/01_no_files_exits_zero.sh new file mode 100755 index 0000000..19bfd8e --- /dev/null +++ b/tests/python/mypy_docker/cases/01_no_files_exits_zero.sh @@ -0,0 +1,20 @@ +#!/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 "mypy_docker_no_files" + +mock_docker_running 0 + +run_hook "python/check_mypy_in_docker.sh" + +assert_exit_code 0 +assert_output_contains "No files to check" + +test_passed diff --git a/tests/python/mypy_docker/cases/02_container_not_running.sh b/tests/python/mypy_docker/cases/02_container_not_running.sh new file mode 100755 index 0000000..9ac47a9 --- /dev/null +++ b/tests/python/mypy_docker/cases/02_container_not_running.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Container not running exits with 1 +# Expected: exit 1, message about container not running +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "mypy_docker_container_stopped" + +mock_docker_stopped + +create_python_fixture "app/service.py" "Service" + +run_hook "python/check_mypy_in_docker.sh" "app/service.py" + +assert_exit_code 1 +assert_output_contains "Container 'app_dev' is not running" + +test_passed diff --git a/tests/python/mypy_docker/cases/03_clean_file_passes.sh b/tests/python/mypy_docker/cases/03_clean_file_passes.sh new file mode 100755 index 0000000..9631b8f --- /dev/null +++ b/tests/python/mypy_docker/cases/03_clean_file_passes.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Clean file passes in Docker +# Expected: exit 0, message about analysis passed +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "mypy_docker_clean_file" + +mock_docker_running 0 "Success: no issues found" + +create_python_fixture "app/clean.py" "CleanClass" + +run_hook "python/check_mypy_in_docker.sh" "app/clean.py" + +assert_exit_code 0 +assert_output_contains "Static analysis passed" + +test_passed diff --git a/tests/python/mypy_docker/run.sh b/tests/python/mypy_docker/run.sh new file mode 100755 index 0000000..7821c65 --- /dev/null +++ b/tests/python/mypy_docker/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Runs all mypy Docker 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 "Mypy Docker tests: $PASSED passed, $FAILED failed" + +[[ $FAILED -eq 0 ]] diff --git a/tests/python/pytest_docker/cases/02_tests_pass.sh b/tests/python/pytest_docker/cases/02_tests_pass.sh new file mode 100755 index 0000000..b7a8edc --- /dev/null +++ b/tests/python/pytest_docker/cases/02_tests_pass.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Tests pass in Docker +# Expected: exit 0, message about all tests passed +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "pytest_docker_pass" + +mock_docker_running 0 "3 passed in 0.12s" + +run_hook "python/check_pytest_in_docker.sh" + +assert_exit_code 0 +assert_output_contains "All tests passed" + +test_passed diff --git a/tests/python/pytest_docker/cases/03_tests_fail.sh b/tests/python/pytest_docker/cases/03_tests_fail.sh new file mode 100755 index 0000000..d1990c5 --- /dev/null +++ b/tests/python/pytest_docker/cases/03_tests_fail.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Tests fail in Docker +# Expected: exit 1, message about tests failed +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "pytest_docker_fail" + +mock_docker_running 1 "1 failed, 2 passed in 0.15s" + +run_hook "python/check_pytest_in_docker.sh" + +assert_exit_code 1 +assert_output_contains "Tests failed" + +test_passed diff --git a/tests/python/pytest_docker/run.sh b/tests/python/pytest_docker/run.sh new file mode 100755 index 0000000..8c3640f --- /dev/null +++ b/tests/python/pytest_docker/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Runs all pytest Docker 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 "Pytest Docker tests: $PASSED passed, $FAILED failed" + +[[ $FAILED -eq 0 ]] diff --git a/tests/python/pytest_local/cases/01_tests_pass.sh b/tests/python/pytest_local/cases/01_tests_pass.sh new file mode 100755 index 0000000..2a6ee28 --- /dev/null +++ b/tests/python/pytest_local/cases/01_tests_pass.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Tests pass (pytest exit 0) +# Expected: exit 0, message about all tests passed +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "pytest_local_pass" + +mock_pytest_cmd 0 "3 passed in 0.12s" + +mkdir -p "tests" + +run_hook "python/check_pytest.sh" + +assert_exit_code 0 +assert_output_contains "All tests passed" + +test_passed diff --git a/tests/python/pytest_local/cases/02_tests_fail.sh b/tests/python/pytest_local/cases/02_tests_fail.sh new file mode 100755 index 0000000..225d67b --- /dev/null +++ b/tests/python/pytest_local/cases/02_tests_fail.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Tests fail (pytest exit 1) +# Expected: exit 1, message about tests failed +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "pytest_local_fail" + +mock_pytest_cmd 1 "1 failed, 2 passed in 0.15s" + +mkdir -p "tests" + +run_hook "python/check_pytest.sh" + +assert_exit_code 1 +assert_output_contains "Tests failed" + +test_passed diff --git a/tests/python/pytest_local/run.sh b/tests/python/pytest_local/run.sh new file mode 100755 index 0000000..5982a10 --- /dev/null +++ b/tests/python/pytest_local/run.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Runs all pytest local 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 "Pytest local tests: $PASSED passed, $FAILED failed" + +[[ $FAILED -eq 0 ]] From 1cc35228650e920ed1f8b01968f3d522dd0d6166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:55:53 +0300 Subject: [PATCH 14/17] fixed tests.yml --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bf7870e..f2c7c8e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,6 +41,7 @@ jobs: - name: Prepare test environment run: | chmod +x php/*.sh + chmod +x python/*.sh chmod +x php/laravel/*.sh 2>/dev/null || true chmod +x shell/*.sh 2>/dev/null || true chmod +x docker/*.sh 2>/dev/null || true From 2931a70e097688295a2f7c510f04e4dd7ab4ca01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:01:05 +0300 Subject: [PATCH 15/17] fixed tests.yml --- .github/workflows/tests.yml | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f2c7c8e..44940d1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,8 +18,8 @@ jobs: run: | find . -name "*.sh" -not -path "./vendor/*" -exec shellcheck {} + - unit-tests: - name: Unit Tests + php-tests: + name: PHP Tests runs-on: ubuntu-latest needs: shellcheck @@ -41,14 +41,28 @@ jobs: - name: Prepare test environment run: | chmod +x php/*.sh - chmod +x python/*.sh chmod +x php/laravel/*.sh 2>/dev/null || true - chmod +x shell/*.sh 2>/dev/null || true - chmod +x docker/*.sh 2>/dev/null || true - chmod +x simple/*.sh 2>/dev/null || true - chmod +x tests/run_all.sh chmod +x tests/lib/*.bash - find tests -name "*.sh" -exec chmod +x {} + + find tests/php -name "*.sh" -exec chmod +x {} + + + - name: Run PHP tests + run: ./tests/php/phpstan/run.sh + + python-tests: + name: Python Tests + runs-on: ubuntu-latest + needs: shellcheck - - name: Run all tests - run: ./tests/run_all.sh + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Prepare test environment + run: | + chmod +x python/*.sh + chmod +x tests/lib/*.bash + find tests/python -name "*.sh" -exec chmod +x {} + + + - name: Run Python tests + run: | + for suite in tests/python/*/run.sh; do "$suite"; done From 8ef9a3be2655459e9fab62916939347921326c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:05:52 +0300 Subject: [PATCH 16/17] fixed check_shellcheck.sh --- scripts/check_shellcheck.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/check_shellcheck.sh b/scripts/check_shellcheck.sh index f617d96..8f4ca41 100644 --- a/scripts/check_shellcheck.sh +++ b/scripts/check_shellcheck.sh @@ -1,7 +1,7 @@ #!/bin/bash # ------------------------------------------------------------------------------ # Runs ShellCheck locally on shell scripts provided as arguments. -# Reports warnings and errors using ShellCheck severity level "warning". +# Reports warnings and errors using ShellCheck. # Fails if any script contains issues. # ------------------------------------------------------------------------------ @@ -30,9 +30,9 @@ check_file() { echo "Checking $FILE..." if [[ -n "$EXCLUDE_STRING" ]]; then - shellcheck --severity=warning --exclude="$EXCLUDE_STRING" "$FILE" || ERROR_FOUND=1 + shellcheck --exclude="$EXCLUDE_STRING" "$FILE" || ERROR_FOUND=1 else - shellcheck --severity=warning "$FILE" || ERROR_FOUND=1 + shellcheck "$FILE" || ERROR_FOUND=1 fi } From f22ff8d9b89eabaf40117197724f9bdb12962228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:07:21 +0300 Subject: [PATCH 17/17] fixed shellcheck errors --- python/check_flake8_in_docker.sh | 4 ++-- python/check_mypy_in_docker.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/check_flake8_in_docker.sh b/python/check_flake8_in_docker.sh index 52be024..b54a232 100755 --- a/python/check_flake8_in_docker.sh +++ b/python/check_flake8_in_docker.sh @@ -34,7 +34,7 @@ check_container_running() { to_container_path() { local file="$1" # Replace app/ with /app/ - echo "$file" | sed "s|^${HOST_APP_PATH}|${CONTAINER_APP_PATH}|" + echo "${file/#${HOST_APP_PATH}/${CONTAINER_APP_PATH}}" } # ----------------------------- @@ -91,7 +91,7 @@ for file in "${PY_FILES[@]}"; do HAS_ERRORS=1 echo "Style errors in: $file" # Convert container paths back to host paths in output - echo "$OUTPUT" | sed "s|${CONTAINER_APP_PATH}|${HOST_APP_PATH}|g" + echo "${OUTPUT//${CONTAINER_APP_PATH}/${HOST_APP_PATH}}" echo "" fi done diff --git a/python/check_mypy_in_docker.sh b/python/check_mypy_in_docker.sh index 37cd7cc..1fba1cf 100755 --- a/python/check_mypy_in_docker.sh +++ b/python/check_mypy_in_docker.sh @@ -21,7 +21,7 @@ check_container_running() { to_container_path() { local file="$1" - echo "$file" | sed "s|^${HOST_APP_PATH}|${CONTAINER_APP_PATH}|" + echo "${file/#${HOST_APP_PATH}/${CONTAINER_APP_PATH}}" } if [ $# -eq 0 ]; then @@ -75,7 +75,7 @@ if [ $EXIT_CODE -ne 0 ]; then echo "----------------------------------------" echo "ERROR: Static analysis failed!" echo "----------------------------------------" - echo "$OUTPUT" | sed "s|${CONTAINER_APP_PATH}|${HOST_APP_PATH}|g" + echo "${OUTPUT//${CONTAINER_APP_PATH}/${HOST_APP_PATH}}" echo "----------------------------------------" echo "Total files checked: ${#PY_FILES[@]}" exit 1