From fd37fa18a5da33c2926e8fe46d0ba45d4591652a Mon Sep 17 00:00:00 2001 From: Matheus Lima Date: Fri, 11 Jul 2025 11:10:57 -0300 Subject: [PATCH 1/2] Adding tests --- .github/workflows/test.yml | 124 ++++++++++++++ CLAUDE.md | 73 ++++++++ run-tests.zsh | 99 +++++++++++ tests/main.test.zsh | 81 +++++++++ tests/providers/anthropic.test.zsh | 116 +++++++++++++ tests/providers/gemini.test.zsh | 122 +++++++++++++ tests/providers/ollama.test.zsh | 123 +++++++++++++ tests/providers/openai.test.zsh | 118 +++++++++++++ tests/test_helper.zsh | 265 +++++++++++++++++++++++++++++ 9 files changed, 1121 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 CLAUDE.md create mode 100755 run-tests.zsh create mode 100644 tests/main.test.zsh create mode 100644 tests/providers/anthropic.test.zsh create mode 100644 tests/providers/gemini.test.zsh create mode 100644 tests/providers/ollama.test.zsh create mode 100644 tests/providers/openai.test.zsh create mode 100644 tests/test_helper.zsh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..cc932f1 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,124 @@ +name: Test + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Install ZSH + run: | + sudo apt-get update + sudo apt-get install -y zsh curl + + - name: Test plugin loading + run: | + zsh -c " + source zsh-git-ai.zsh 2>/dev/null || true + if [[ -z \"\$ZSH_GIT_AI_PROVIDER\" ]]; then + echo 'ERROR: Plugin failed to load' + exit 1 + fi + echo '✓ Plugin loaded successfully' + echo \" Provider: \$ZSH_GIT_AI_PROVIDER\" + " + + - name: Test provider validation + run: | + zsh -c " + source zsh-git-ai.zsh 2>/dev/null || true + + # Test anthropic provider validation + export ZSH_GIT_AI_PROVIDER='anthropic' + export ANTHROPIC_API_KEY='test-key' + + # Load provider and check requirements + source lib/providers/anthropic.zsh + if anthropic_check_requirements >/dev/null 2>&1; then + echo '✓ Anthropic provider validation passed' + else + echo 'ERROR: Anthropic provider validation failed' + exit 1 + fi + + # Test openai provider validation + export ZSH_GIT_AI_PROVIDER='openai' + export OPENAI_API_KEY='test-key' + source lib/providers/openai.zsh + if openai_check_requirements >/dev/null 2>&1; then + echo '✓ OpenAI provider validation passed' + else + echo 'ERROR: OpenAI provider validation failed' + exit 1 + fi + + # Test gemini provider validation + export ZSH_GIT_AI_PROVIDER='gemini' + export GEMINI_API_KEY='test-key' + source lib/providers/gemini.zsh + if gemini_check_requirements >/dev/null 2>&1; then + echo '✓ Gemini provider validation passed' + else + echo 'ERROR: Gemini provider validation failed' + exit 1 + fi + + # Test ollama provider validation + export ZSH_GIT_AI_PROVIDER='ollama' + source lib/providers/ollama.zsh + # Ollama doesn't require API key, just check loading + echo '✓ Ollama provider loaded' + " + + - name: Test load_provider function + run: | + zsh -c " + source zsh-git-ai.zsh 2>/dev/null || true + + # Test loading valid provider + export ANTHROPIC_API_KEY='test-key' + if load_provider 'anthropic' >/dev/null 2>&1; then + echo '✓ Valid provider loads successfully' + else + echo 'ERROR: Failed to load valid provider' + exit 1 + fi + + # Test loading invalid provider + if ! load_provider 'invalid' 2>/dev/null; then + echo '✓ Invalid provider correctly rejected' + else + echo 'ERROR: Invalid provider not rejected' + exit 1 + fi + " + + - name: Run test suite + run: | + # Make test runner executable + chmod +x run-tests.zsh + # Run all tests + ./run-tests.zsh + + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Install shellcheck + run: | + sudo apt-get update + sudo apt-get install -y shellcheck + + - name: Run shellcheck + run: | + # Shellcheck all shell scripts (zsh files use bash compatibility mode) + find . -name "*.zsh" -o -name "*.sh" | grep -v temp-zsh-ai | xargs shellcheck -s bash -e SC1090,SC1091,SC2034,SC2154 || true \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3873702 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,73 @@ +# Project Development Guide for git-commit-ai + +## Overview +This is a lightweight ZSH tool that generates git commit messages using various AI providers (Anthropic, OpenAI, Gemini, Ollama). The codebase is pure shell script with zero runtime dependencies. + +## Testing + +### Running Tests +```bash +# Run all tests +./run-tests.zsh + +# Run specific test directory +./run-tests.zsh tests/providers + +# Run single test file +zsh tests/providers/anthropic.test.zsh +``` + +### Test Structure +- Tests are located in `tests/` directory +- Provider-specific tests in `tests/providers/` +- Test helper utilities in `tests/test_helper.zsh` +- Custom ZSH-based testing framework with mocking support + +### Writing Tests +- Follow existing test patterns in test files +- Use `test_helper.zsh` functions for assertions and mocking +- Each test should have proper setup/teardown +- Mock external commands (curl, jq) rather than making real API calls + +## Development Workflow + +### Before Committing +1. Run all tests: `./run-tests.zsh` +2. Verify shellcheck passes (runs automatically in CI) +3. Test your changes manually with different providers +4. Ensure no API keys or sensitive data are committed + +### Code Style +- Follow existing ZSH scripting patterns +- Use meaningful variable names with proper scoping +- Add error handling for all external commands +- Keep functions small and focused +- Maintain backwards compatibility with older ZSH versions + +### Provider Implementation +When adding/modifying AI providers: +1. Implement provider in `lib/providers/` directory +2. Add comprehensive tests in `tests/providers/` +3. Update documentation if adding new configuration options +4. Test with actual API calls (use your own keys) +5. Ensure provider follows the standard interface: + - `{provider}_check_requirements` function + - `{provider}_generate_commit_message` function accepting diff and status + +### Common Tasks +- **Adding a new provider**: Copy existing provider pattern, add tests +- **Modifying commit generation**: Update provider logic and test with various diff formats +- **Changing main script logic**: Update `zsh-git-ai.zsh` and test in `tests/main.test.zsh` + +### Important Notes +- This is a shell-only project - no Node.js, Python, or other runtimes +- Keep the codebase lightweight +- Preserve zero-dependency philosophy (jq is optional) +- CI runs on every PR - ensure tests pass before merging +- All providers should gracefully handle missing jq by falling back to sed/grep + +### Testing Commands +When you're done with changes, please run the linter to check if there are any errors: +```bash +npm run lint +``` \ No newline at end of file diff --git a/run-tests.zsh b/run-tests.zsh new file mode 100755 index 0000000..6764771 --- /dev/null +++ b/run-tests.zsh @@ -0,0 +1,99 @@ +#!/usr/bin/env zsh + +# Test runner for git-commit-ai +# Runs all test files and reports results + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Counters +TOTAL_TESTS=0 +TOTAL_PASSED=0 +TOTAL_FAILED=0 +FAILED_FILES=() + +# Run test file and capture results +run_test_file() { + local test_file="$1" + echo -e "${YELLOW}Running ${test_file}...${NC}" + + # Run test and capture output + local output + local exit_code + + output=$(zsh "$test_file" 2>&1) + exit_code=$? + + # Display output + echo "$output" + + # Count test results from output + local passed=$(echo "$output" | grep -c "✓") + local failed=$(echo "$output" | grep -c "✗") + + # Update totals + if [[ $passed -gt 0 || $failed -gt 0 ]]; then + TOTAL_TESTS=$((TOTAL_TESTS + passed + failed)) + TOTAL_PASSED=$((TOTAL_PASSED + passed)) + TOTAL_FAILED=$((TOTAL_FAILED + failed)) + fi + + # Track failed files + if [[ $exit_code -ne 0 || $failed -gt 0 ]]; then + FAILED_FILES+=("$test_file") + fi + + echo "" +} + +# Main function +main() { + local test_dir="${1:-tests}" + + echo -e "${BLUE}Running git-commit-ai tests...${NC}" + echo "" + + # Find all test files + local test_files=($test_dir/**/*.test.zsh(N)) + + if [[ ${#test_files} -eq 0 ]]; then + echo -e "${YELLOW}No test files found in $test_dir${NC}" + exit 1 + fi + + # Run each test file + for test_file in $test_files; do + run_test_file "$test_file" + done + + # Summary + echo "================================" + echo -e "${BLUE}Test Summary:${NC}" + echo -e " Total Tests: $TOTAL_TESTS" + echo -e " ${GREEN}Passed: $TOTAL_PASSED${NC}" + echo -e " ${RED}Failed: $TOTAL_FAILED${NC}" + + if [[ ${#FAILED_FILES} -gt 0 ]]; then + echo -e "\n${RED}Failed test files:${NC}" + for file in $FAILED_FILES; do + echo " - $file" + done + fi + echo "================================" + + # Exit based on results + if [[ $TOTAL_FAILED -gt 0 ]]; then + exit 1 + else + exit 0 + fi +} + +# Run if executed directly +if [[ "${ZSH_EVAL_CONTEXT}" == "toplevel" ]]; then + main "$@" +fi \ No newline at end of file diff --git a/tests/main.test.zsh b/tests/main.test.zsh new file mode 100644 index 0000000..b154eeb --- /dev/null +++ b/tests/main.test.zsh @@ -0,0 +1,81 @@ +#!/usr/bin/env zsh + +# Load test helper +source "${0:A:h}/test_helper.zsh" + +# Test main script functionality +test_default_provider() { + setup_test_env + unset ZSH_GIT_AI_PROVIDER + source "$PLUGIN_DIR/zsh-git-ai.zsh" 2>/dev/null || true + # Default should be anthropic + assert_equals "$ZSH_GIT_AI_PROVIDER" "anthropic" + teardown_test_env +} + +test_load_provider_success() { + setup_test_env + export ZSH_GIT_AI_PROVIDER="anthropic" + export ANTHROPIC_API_KEY="test-key" + + # Mock the provider check requirements function + anthropic_check_requirements() { + return 0 + } + + # Source just the load_provider function + source "$PLUGIN_DIR/zsh-git-ai.zsh" 2>/dev/null || true + local result + load_provider "anthropic" >/dev/null 2>&1 + result=$? + + assert_equals "$result" "0" + teardown_test_env +} + +test_load_provider_invalid() { + setup_test_env + export ZSH_GIT_AI_PROVIDER="invalid_provider" + + # Source just the load_provider function + source "$PLUGIN_DIR/zsh-git-ai.zsh" 2>/dev/null || true + local output + output=$(load_provider "invalid_provider" 2>&1) + local result=$? + + assert_equals "$result" "1" + assert_contains "$output" "Provider 'invalid_provider' not found" + teardown_test_env +} + +test_spinner_function_exists() { + setup_test_env + source "$PLUGIN_DIR/zsh-git-ai.zsh" 2>/dev/null || true + + # Check if show_spinner function is defined + if type show_spinner >/dev/null 2>&1; then + echo "✓ show_spinner function exists" + else + echo "✗ show_spinner function not found" + return 1 + fi + + teardown_test_env +} + +# Run tests +echo "Testing main script..." +test_default_provider +test_load_provider_success +test_load_provider_invalid +test_spinner_function_exists + +# Hardcoded failing test to verify CI catches failures +test_intentional_failure() { + setup_test_env + echo "✗ This test is intentionally failing to verify CI" + assert_equals "expected" "actual" + teardown_test_env +} + +test_intentional_failure \ No newline at end of file diff --git a/tests/providers/anthropic.test.zsh b/tests/providers/anthropic.test.zsh new file mode 100644 index 0000000..64f7305 --- /dev/null +++ b/tests/providers/anthropic.test.zsh @@ -0,0 +1,116 @@ +#!/usr/bin/env zsh + +# Load test helper +source "${0:A:h:h}/test_helper.zsh" + +# Load the anthropic provider +source "$PLUGIN_DIR/lib/providers/anthropic.zsh" + +# Test check requirements +test_anthropic_check_requirements_with_key() { + setup_test_env + export ANTHROPIC_API_KEY="test-key" + + anthropic_check_requirements >/dev/null 2>&1 + local result=$? + + assert_equals "$result" "0" + teardown_test_env +} + +test_anthropic_check_requirements_without_key() { + setup_test_env + unset ANTHROPIC_API_KEY + + local output + output=$(anthropic_check_requirements 2>&1) + local result=$? + + assert_equals "$result" "1" + assert_contains "$output" "ANTHROPIC_API_KEY is not set" + teardown_test_env +} + +test_anthropic_generate_commit_message_success() { + setup_test_env + export ANTHROPIC_API_KEY="test-key" + + # Define diff output + local diff_output="diff --git a/test.txt b/test.txt +index 1234567..abcdefg 100644 +--- a/test.txt ++++ b/test.txt +@@ -1 +1 @@ +-Hello World ++Hello AI World" + + # Mock jq availability + mock_jq true + + # Mock successful curl response + mock_curl_response '{ + "content": [ + { + "text": "feat: update greeting message\n\nChanged greeting from \"Hello World\" to \"Hello AI World\"" + } + ] + }' + + local output + output=$(anthropic_generate_commit_message "$diff_output" "On branch main" 2>&1) + local result=$? + + assert_equals "$result" "0" + assert_contains "$output" "feat: update greeting message" + + teardown_test_env +} + +test_anthropic_generate_commit_message_api_error() { + setup_test_env + export ANTHROPIC_API_KEY="test-key" + + # Define diff output + local diff_output="some changes" + + # Mock error response + mock_curl_response '{ + "error": { + "message": "Invalid API key" + } + }' 0 + + local output + output=$(anthropic_generate_commit_message "$diff_output" "On branch main" 2>&1) + local result=$? + + assert_equals "$result" "1" + assert_contains "$output" "Failed to generate commit message" + + teardown_test_env +} + +test_anthropic_generate_commit_message_no_changes() { + setup_test_env + export ANTHROPIC_API_KEY="test-key" + + # Define empty diff output + local diff_output="" + + local output + output=$(anthropic_generate_commit_message "$diff_output" "On branch main" 2>&1) + local result=$? + + assert_equals "$result" "1" + assert_contains "$output" "Failed to generate commit message" + + teardown_test_env +} + +# Run tests +echo "Testing Anthropic provider..." +test_anthropic_check_requirements_with_key +test_anthropic_check_requirements_without_key +test_anthropic_generate_commit_message_success +test_anthropic_generate_commit_message_api_error +test_anthropic_generate_commit_message_no_changes \ No newline at end of file diff --git a/tests/providers/gemini.test.zsh b/tests/providers/gemini.test.zsh new file mode 100644 index 0000000..8e3033d --- /dev/null +++ b/tests/providers/gemini.test.zsh @@ -0,0 +1,122 @@ +#!/usr/bin/env zsh + +# Load test helper +source "${0:A:h:h}/test_helper.zsh" + +# Load the gemini provider +source "$PLUGIN_DIR/lib/providers/gemini.zsh" + +# Test check requirements +test_gemini_check_requirements_with_key() { + setup_test_env + export GEMINI_API_KEY="test-key" + + gemini_check_requirements >/dev/null 2>&1 + local result=$? + + assert_equals "$result" "0" + teardown_test_env +} + +test_gemini_check_requirements_without_key() { + setup_test_env + unset GEMINI_API_KEY + + local output + output=$(gemini_check_requirements 2>&1) + local result=$? + + assert_equals "$result" "1" + assert_contains "$output" "GEMINI_API_KEY is not set" + teardown_test_env +} + +test_gemini_generate_commit_message_success() { + setup_test_env + export GEMINI_API_KEY="test-key" + + # Define diff output + local diff_output="diff --git a/test.txt b/test.txt +index 1234567..abcdefg 100644 +--- a/test.txt ++++ b/test.txt +@@ -1 +1 @@ +-Hello World ++Hello AI World" + + # Mock jq availability + mock_jq true + + # Mock successful curl response + mock_curl_response '{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "feat: update greeting message\n\nChanged greeting from \"Hello World\" to \"Hello AI World\"" + } + ] + } + } + ] + }' + + local output + output=$(gemini_generate_commit_message "$diff_output" "On branch main" 2>&1) + local result=$? + + assert_equals "$result" "0" + assert_contains "$output" "feat: update greeting message" + + teardown_test_env +} + +test_gemini_generate_commit_message_api_error() { + setup_test_env + export GEMINI_API_KEY="test-key" + + # Define diff output + local diff_output="some changes" + + # Mock error response + mock_curl_response '{ + "error": { + "message": "Invalid API key" + } + }' 0 + + local output + output=$(gemini_generate_commit_message "$diff_output" "On branch main" 2>&1) + local result=$? + + assert_equals "$result" "1" + assert_contains "$output" "Failed to generate commit message" + + teardown_test_env +} + +test_gemini_generate_commit_message_no_changes() { + setup_test_env + export GEMINI_API_KEY="test-key" + + # Define empty diff output + local diff_output="" + + local output + output=$(gemini_generate_commit_message "$diff_output" "On branch main" 2>&1) + local result=$? + + assert_equals "$result" "1" + assert_contains "$output" "Failed to generate commit message" + + teardown_test_env +} + +# Run tests +echo "Testing Gemini provider..." +test_gemini_check_requirements_with_key +test_gemini_check_requirements_without_key +test_gemini_generate_commit_message_success +test_gemini_generate_commit_message_api_error +test_gemini_generate_commit_message_no_changes \ No newline at end of file diff --git a/tests/providers/ollama.test.zsh b/tests/providers/ollama.test.zsh new file mode 100644 index 0000000..087863d --- /dev/null +++ b/tests/providers/ollama.test.zsh @@ -0,0 +1,123 @@ +#!/usr/bin/env zsh + +# Load test helper +source "${0:A:h:h}/test_helper.zsh" + +# Load the ollama provider +source "$PLUGIN_DIR/lib/providers/ollama.zsh" + +# Test check requirements +test_ollama_check_requirements_success() { + setup_test_env + + # Mock curl to simulate ollama being available + mock_command "curl" "" 0 + + ollama_check_requirements >/dev/null 2>&1 + local result=$? + + assert_equals "$result" "0" + teardown_test_env +} + +test_ollama_check_requirements_not_running() { + setup_test_env + + # Mock curl to simulate ollama not being available + mock_command "curl" "" 7 # Connection refused + + local output + output=$(ollama_check_requirements 2>&1) + local result=$? + + assert_equals "$result" "1" + assert_contains "$output" "Cannot connect to Ollama" + teardown_test_env +} + +test_ollama_generate_commit_message_success() { + setup_test_env + + # Define diff output + local diff_output="diff --git a/test.txt b/test.txt +index 1234567..abcdefg 100644 +--- a/test.txt ++++ b/test.txt +@@ -1 +1 @@ +-Hello World ++Hello AI World" + + # Mock jq availability + mock_jq true + + # Mock successful curl response + mock_curl_response '{ + "response": "feat: update greeting message\n\nChanged greeting from \"Hello World\" to \"Hello AI World\"" + }' + + local output + output=$(ollama_generate_commit_message "$diff_output" "On branch main" 2>&1) + local result=$? + + assert_equals "$result" "0" + assert_contains "$output" "feat: update greeting message" + + teardown_test_env +} + +test_ollama_generate_commit_message_api_error() { + setup_test_env + + # Define diff output + local diff_output="some changes" + + # Mock error response + mock_curl_response '{ + "error": "Model not found" + }' 0 + + local output + output=$(ollama_generate_commit_message "$diff_output" "On branch main" 2>&1) + local result=$? + + assert_equals "$result" "1" + assert_contains "$output" "Failed to generate commit message" + + teardown_test_env +} + +test_ollama_generate_commit_message_no_changes() { + setup_test_env + + # Define empty diff output + local diff_output="" + + local output + output=$(ollama_generate_commit_message "$diff_output" "On branch main" 2>&1) + local result=$? + + assert_equals "$result" "1" + assert_contains "$output" "Failed to generate commit message" + + teardown_test_env +} + +test_ollama_custom_model() { + setup_test_env + export ZSH_GIT_AI_OLLAMA_MODEL="codellama" + + # Verify model is set correctly + source "$PLUGIN_DIR/lib/providers/ollama.zsh" + assert_equals "$ZSH_GIT_AI_OLLAMA_MODEL" "codellama" + + teardown_test_env +} + +# Run tests +echo "Testing Ollama provider..." +test_ollama_check_requirements_success +test_ollama_check_requirements_not_running +test_ollama_generate_commit_message_success +test_ollama_generate_commit_message_api_error +test_ollama_generate_commit_message_no_changes +test_ollama_custom_model \ No newline at end of file diff --git a/tests/providers/openai.test.zsh b/tests/providers/openai.test.zsh new file mode 100644 index 0000000..1be669c --- /dev/null +++ b/tests/providers/openai.test.zsh @@ -0,0 +1,118 @@ +#!/usr/bin/env zsh + +# Load test helper +source "${0:A:h:h}/test_helper.zsh" + +# Load the openai provider +source "$PLUGIN_DIR/lib/providers/openai.zsh" + +# Test check requirements +test_openai_check_requirements_with_key() { + setup_test_env + export OPENAI_API_KEY="test-key" + + openai_check_requirements >/dev/null 2>&1 + local result=$? + + assert_equals "$result" "0" + teardown_test_env +} + +test_openai_check_requirements_without_key() { + setup_test_env + unset OPENAI_API_KEY + + local output + output=$(openai_check_requirements 2>&1) + local result=$? + + assert_equals "$result" "1" + assert_contains "$output" "OPENAI_API_KEY is not set" + teardown_test_env +} + +test_openai_generate_commit_message_success() { + setup_test_env + export OPENAI_API_KEY="test-key" + + # Define diff output + local diff_output="diff --git a/test.txt b/test.txt +index 1234567..abcdefg 100644 +--- a/test.txt ++++ b/test.txt +@@ -1 +1 @@ +-Hello World ++Hello AI World" + + # Mock jq availability + mock_jq true + + # Mock successful curl response + mock_curl_response '{ + "choices": [ + { + "message": { + "content": "feat: update greeting message\n\nChanged greeting from \"Hello World\" to \"Hello AI World\"" + } + } + ] + }' + + local output + output=$(openai_generate_commit_message "$diff_output" "On branch main" 2>&1) + local result=$? + + assert_equals "$result" "0" + assert_contains "$output" "feat: update greeting message" + + teardown_test_env +} + +test_openai_generate_commit_message_api_error() { + setup_test_env + export OPENAI_API_KEY="test-key" + + # Define diff output + local diff_output="some changes" + + # Mock error response + mock_curl_response '{ + "error": { + "message": "Invalid API key" + } + }' 0 + + local output + output=$(openai_generate_commit_message "$diff_output" "On branch main" 2>&1) + local result=$? + + assert_equals "$result" "1" + assert_contains "$output" "Failed to generate commit message" + + teardown_test_env +} + +test_openai_generate_commit_message_no_changes() { + setup_test_env + export OPENAI_API_KEY="test-key" + + # Define empty diff output + local diff_output="" + + local output + output=$(openai_generate_commit_message "$diff_output" "On branch main" 2>&1) + local result=$? + + assert_equals "$result" "1" + assert_contains "$output" "Failed to generate commit message" + + teardown_test_env +} + +# Run tests +echo "Testing OpenAI provider..." +test_openai_check_requirements_with_key +test_openai_check_requirements_without_key +test_openai_generate_commit_message_success +test_openai_generate_commit_message_api_error +test_openai_generate_commit_message_no_changes \ No newline at end of file diff --git a/tests/test_helper.zsh b/tests/test_helper.zsh new file mode 100644 index 0000000..8224304 --- /dev/null +++ b/tests/test_helper.zsh @@ -0,0 +1,265 @@ +#!/usr/bin/env zsh +# Test helper utilities for git-commit-ai tests + +# Source the plugin files +export ZSH_GIT_AI_TEST_MODE=1 +PLUGIN_DIR="${0:A:h:h}" + +# Mock functions storage +typeset -gA MOCKED_COMMANDS +typeset -gA MOCK_OUTPUTS +typeset -gA MOCK_EXIT_CODES +typeset -gA CALL_COUNTS + +# Initialize mock for a command +mock_command() { + local cmd="$1" + local output="${2:-}" + local exit_code="${3:-0}" + + MOCKED_COMMANDS[$cmd]=1 + MOCK_OUTPUTS[$cmd]="$output" + MOCK_EXIT_CODES[$cmd]="$exit_code" + CALL_COUNTS[$cmd]=0 + + # Create function to override command + eval " + $cmd() { + CALL_COUNTS[$cmd]=\$((CALL_COUNTS[$cmd] + 1)) + if [[ -n \"\${MOCK_OUTPUTS[$cmd]}\" ]]; then + echo \"\${MOCK_OUTPUTS[$cmd]}\" + fi + return \${MOCK_EXIT_CODES[$cmd]} + } + " +} + +# Restore mocked command +unmock_command() { + local cmd="$1" + unset "MOCKED_COMMANDS[$cmd]" + unset "MOCK_OUTPUTS[$cmd]" + unset "MOCK_EXIT_CODES[$cmd]" + unset "CALL_COUNTS[$cmd]" + unfunction "$cmd" 2>/dev/null +} + +# Reset all mocks +reset_mocks() { + for cmd in ${(k)MOCKED_COMMANDS}; do + unmock_command "$cmd" + done +} + +# Assert command was called +assert_called() { + local cmd="$1" + local expected_times="${2:-1}" + local actual_times="${CALL_COUNTS[$cmd]:-0}" + + if [[ $actual_times -ne $expected_times ]]; then + echo "✗ Expected $cmd to be called $expected_times times, but was called $actual_times times" + return 1 + fi + echo "✓ $cmd was called $expected_times times" +} + +# Mock curl for API requests +mock_curl_response() { + local response="$1" + local exit_code="${2:-0}" + + # Store response and exit code in global variables + MOCK_CURL_RESPONSE="$response" + MOCK_CURL_EXIT_CODE="$exit_code" + + # Create a global curl function + function curl() { + if [[ "$MOCK_CURL_EXIT_CODE" -ne 0 ]]; then + return "$MOCK_CURL_EXIT_CODE" + fi + echo "$MOCK_CURL_RESPONSE" + return 0 + } +} + +# Mock jq command +mock_jq() { + local available="${1:-true}" + if [[ "$available" == "true" ]]; then + # Mock command -v jq to return success + command() { + if [[ "$1" == "-v" ]] && [[ "$2" == "jq" ]]; then + echo "/usr/bin/jq" + return 0 + fi + builtin command "$@" + } + # Also mock jq itself to parse JSON properly + jq() { + local args="$@" + local input=$(cat) + # Handle -r flag + local raw_output=false + if [[ "$args" == *"-r"* ]]; then + raw_output=true + args="${args//-r/}" + fi + + # Parse based on the jq query + local result="" + if [[ "$args" == *".content[0].text"* ]]; then + # For Anthropic responses + result=$(echo "$input" | perl -0777 -ne 'if (/"text":\s*"([^"\\]*(\\.[^"\\]*)*)"/) { $val = $1; $val =~ s/\\n/\n/g; $val =~ s/\\"/"/g; print $val; }') + elif [[ "$args" == *".error.message"* ]]; then + # For error responses + result=$(echo "$input" | sed -n 's/.*"message":"\([^"]*\(\\.[^"]*\)*\)".*/\1/p' | sed 's/\\"/"/g') + elif [[ "$args" == *".choices[0].message.content"* ]]; then + # For OpenAI responses + result=$(echo "$input" | perl -0777 -ne 'if (/"content":\s*"([^"\\]*(\\.[^"\\]*)*)"/) { $val = $1; $val =~ s/\\n/\n/g; $val =~ s/\\"/"/g; print $val; }') + elif [[ "$args" == *".candidates[0].content.parts[0].text"* ]]; then + # For Gemini responses + result=$(echo "$input" | perl -0777 -ne 'if (/"text":\s*"([^"\\]*(\\.[^"\\]*)*)"/) { $val = $1; $val =~ s/\\n/\n/g; $val =~ s/\\"/"/g; print $val; }') + elif [[ "$args" == *".response"* ]]; then + # For Ollama responses + result=$(echo "$input" | perl -0777 -ne 'if (/"response":\s*"([^"\\]*(\\.[^"\\]*)*)"/) { $val = $1; $val =~ s/\\n/\n/g; $val =~ s/\\"/"/g; print $val; }') + fi + + # Return result or empty based on // empty handling + if [[ -n "$result" ]]; then + echo "$result" + elif [[ "$args" == *"// empty"* ]]; then + # jq returns nothing for empty + return 0 + else + return 1 + fi + } + else + command() { + if [[ "$1" == "-v" ]] && [[ "$2" == "jq" ]]; then + return 1 + fi + builtin command "$@" + } + fi +} + +# Mock git commands +mock_git_diff() { + local diff_output="${1:-}" + git() { + if [[ "$1" == "diff" ]]; then + shift + echo "$diff_output" + return 0 + fi + builtin command git "$@" + } +} + +mock_git_status() { + local status_output="${1:-}" + git() { + if [[ "$1" == "status" ]]; then + shift + echo "$status_output" + return 0 + fi + builtin command git "$@" + } +} + +# Setup test environment +setup_test_env() { + # Set test environment variables + export ZSH_GIT_AI_PROVIDER="" + export ANTHROPIC_API_KEY="" + export OPENAI_API_KEY="" + export GEMINI_API_KEY="" + export ZSH_GIT_AI_MODEL="" + + # Reset mocks + reset_mocks +} + +# Teardown test environment +teardown_test_env() { + reset_mocks + # Unfunction any mocked commands + unfunction command 2>/dev/null + unfunction jq 2>/dev/null + unfunction curl 2>/dev/null + unfunction git 2>/dev/null + unset ZSH_GIT_AI_PROVIDER + unset ANTHROPIC_API_KEY + unset OPENAI_API_KEY + unset GEMINI_API_KEY + unset ZSH_GIT_AI_MODEL + unset ZSH_GIT_AI_TEST_MODE +} + +# Assert string contains +assert_contains() { + local haystack="$1" + local needle="$2" + if [[ ! "$haystack" == *"$needle"* ]]; then + echo "✗ Expected '$haystack' to contain '$needle'" + return 1 + fi + echo "✓ String contains expected value" +} + +# Assert string equals +assert_equals() { + local actual="$1" + local expected="$2" + if [[ "$actual" != "$expected" ]]; then + echo "✗ Expected '$expected' but got '$actual'" + return 1 + fi + echo "✓ Values are equal" +} + +# Assert string does not contain +assert_not_contains() { + local haystack="$1" + local needle="$2" + if [[ "$haystack" == *"$needle"* ]]; then + echo "✗ Expected '$haystack' to not contain '$needle'" + return 1 + fi + echo "✓ String does not contain value" +} + +# Assert greater than +assert_greater_than() { + local actual="$1" + local expected="$2" + if [[ "$actual" -le "$expected" ]]; then + echo "✗ Expected '$actual' to be greater than '$expected'" + return 1 + fi + echo "✓ Value is greater than expected" +} + +# Capture function output +capture_output() { + local func="$1" + shift + local output + output=$("$func" "$@" 2>&1) + echo "$output" +} + +# Create temporary test directory +create_test_dir() { + local test_dir=$(mktemp -d) + echo "$test_dir" +} + +# Cleanup temporary test directory +cleanup_test_dir() { + local test_dir="$1" + [[ -d "$test_dir" ]] && rm -rf "$test_dir" +} \ No newline at end of file From ba90a8a7be9efbc4bd9f620d599b796b0a68b726 Mon Sep 17 00:00:00 2001 From: Matheus Lima Date: Fri, 11 Jul 2025 11:14:20 -0300 Subject: [PATCH 2/2] Remove test fixtures and outdated documentation --- CLAUDE.md | 5 ----- tests/main.test.zsh | 12 +----------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3873702..7ce7b8b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -66,8 +66,3 @@ When adding/modifying AI providers: - CI runs on every PR - ensure tests pass before merging - All providers should gracefully handle missing jq by falling back to sed/grep -### Testing Commands -When you're done with changes, please run the linter to check if there are any errors: -```bash -npm run lint -``` \ No newline at end of file diff --git a/tests/main.test.zsh b/tests/main.test.zsh index b154eeb..7447a60 100644 --- a/tests/main.test.zsh +++ b/tests/main.test.zsh @@ -68,14 +68,4 @@ echo "Testing main script..." test_default_provider test_load_provider_success test_load_provider_invalid -test_spinner_function_exists - -# Hardcoded failing test to verify CI catches failures -test_intentional_failure() { - setup_test_env - echo "✗ This test is intentionally failing to verify CI" - assert_equals "expected" "actual" - teardown_test_env -} - -test_intentional_failure \ No newline at end of file +test_spinner_function_exists \ No newline at end of file