feat: enhance version validation with semver range support and test suite

- Add dual-mode version extraction supporting branch and file validation
- Implement comprehensive semver range validation (caret, tilde, wildcards)
- Extract release creation logic to dedicated script for reusability
- Add test suite with comprehensive coverage for validation scripts
- Update workflow to validate new version is greater than current
- Bump version to 1.1.0 in pyproject.toml to match version.py

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
github-actions[bot]
2025-10-27 13:18:11 +11:00
parent 08dab4ca6b
commit 05eb16a07b
8 changed files with 1281 additions and 69 deletions

187
.github/scripts/test/README.md vendored Normal file
View File

@@ -0,0 +1,187 @@
# Script Tests
This directory contains test suites for the scripts in `.github/scripts/`.
## Running Tests
### Run All Tests
Run all test suites:
```bash
# Run semver validation tests
.github/scripts/test/test_validate_semver.sh
# Run GitHub Actions output tests
.github/scripts/test/test_github_output.sh
```
### Test validate_semver.sh
Run the comprehensive test suite for the SemVer validation script:
```bash
.github/scripts/test/test_validate_semver.sh
```
The test suite includes **98 test cases** covering:
1. **Basic SemVer Format Validation** (18 tests)
- Valid stable versions (1.2.3, 0.0.0, etc.)
- Valid pre-release versions (1.0.0-alpha, 1.0.0-rc.1, etc.)
- Valid build metadata (1.0.0+build, 1.0.0-beta+exp.sha.5114f85)
- Invalid formats (missing components, leading zeros, non-numeric)
2. **Comparison Operators** (14 tests)
- Greater than or equal (`>=`)
- Greater than (`>`)
- Less than or equal (`<=`)
- Less than (`<`)
- Equal (`=`)
3. **Caret Ranges** (13 tests)
- Standard caret ranges (`^2.0.0`)
- Caret with 0.x versions (`^0.2.3`)
- Caret with 0.0.x versions (`^0.0.3`)
- Pre-release handling
4. **Tilde Ranges** (8 tests)
- Standard tilde ranges (`~1.2.0`)
- Tilde with different versions
5. **Wildcard Ranges** (14 tests)
- Major wildcards (`1.x`, `2.X`, `3.*`)
- Minor wildcards (`1.5.x`)
- Full wildcards (`1.x.x`)
- Pre-release with wildcards
6. **Compound Ranges** (10 tests)
- AND ranges (`>=1.0.0 <2.0.0`)
- Multiple constraints (`>1.0.0 <=2.0.0`)
7. **Pre-release Version Comparisons** (8 tests)
- Pre-release ordering
- Pre-release with ranges
8. **Build Metadata** (4 tests)
- Build metadata is ignored in comparisons
9. **Edge Cases** (9 tests)
- Boundary conditions
- Large version numbers
- Zero versions
## Test Output
The test script provides colorized output:
- ✓ (green) - Test passed
- ✗ (red) - Test failed
- Blue section headers
- Summary statistics at the end
Example output:
```text
Running validate_semver.sh tests...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Test 1: Basic SemVer Format Validation
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ Valid stable version: 1.2.3
✓ Valid stable version: 0.0.0
...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Test Results Summary
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total tests run: 98
Tests passed: 98
Tests failed: 0
✓ All tests passed!
```
## Exit Codes
- `0` - All tests passed
- `1` - One or more tests failed
## Adding New Tests
To add new test cases, edit [test_validate_semver.sh](test_validate_semver.sh) and use the `run_test` helper function:
```bash
run_test "test description" "version" "range" "should_pass"
```
Parameters:
- `test_description`: Human-readable test name
- `version`: The semver version to test
- `range`: The range to validate against (optional, use `""` for none)
- `should_pass`: `"true"` if test should pass, `"false"` if it should fail
Example:
```bash
run_test "1.2.3 >= 1.0.0" "1.2.3" ">=1.0.0" "true"
run_test "1.2.3 >= 2.0.0" "1.2.3" ">=2.0.0" "false"
```
### Test GITHUB_OUTPUT Functionality
Run tests specifically for GitHub Actions output integration:
```bash
.github/scripts/test/test_github_output.sh
```
The GITHUB_OUTPUT test suite includes **25 test cases** covering:
1. **Basic Output Without Range** (4 tests)
- Stable version outputs
- Large version numbers
2. **Pre-release Version Output** (4 tests)
- Alpha, beta, rc versions
- Complex pre-release identifiers
3. **Build Metadata in Output** (3 tests)
- Verifies build metadata is preserved in output
- Stable and pre-release with metadata
4. **Range Validation Output** (5 tests)
- Output when version satisfies ranges
- Tests `range_satisfied` field
5. **Pre-release with Range Output** (2 tests)
- Pre-release versions with range constraints
6. **Edge Cases** (3 tests)
- Boundary conditions
- Complex compound ranges
7. **Output File Format Validation** (3 tests)
- Validates key=value format
- Correct number of output lines (2 without range, 3 with range)
8. **No Output When GITHUB_OUTPUT Not Set** (1 test)
- Ensures no file created when env var not set
**Output Fields:**
- `version` - The validated semver version
- `is_prerelease` - Boolean (`true`/`false`) indicating if version is pre-release
- `range_satisfied` - Boolean (`true`/`false`) indicating if version satisfies range (only when range provided)
## CI Integration
These tests can be integrated into CI/CD pipelines:
```yaml
- name: Run script tests
run: |
.github/scripts/test/test_validate_semver.sh
.github/scripts/test/test_github_output.sh
```

419
.github/scripts/test/test_github_output.sh vendored Executable file
View File

@@ -0,0 +1,419 @@
#!/usr/bin/env bash
set -euo pipefail
# Test script for validate_semver.sh GitHub Actions output functionality
# Usage: ./test_github_output.sh
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Get the directory of this test script
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_DIR="$(dirname "$TEST_DIR")"
SCRIPT="$SCRIPT_DIR/validate_semver.sh"
# Test counters
TESTS_RUN=0
TESTS_PASSED=0
TESTS_FAILED=0
# Create temp directory for test outputs
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT
# Test helper function
test_github_output() {
local test_name="$1"
local version="$2"
local range="${3:-}"
local expected_version="$4"
local expected_is_prerelease="$5"
local expected_range_satisfied="${6:-}"
TESTS_RUN=$((TESTS_RUN + 1))
# Create a temporary output file
local output_file="$TEMP_DIR/github_output_${TESTS_RUN}.txt"
export GITHUB_OUTPUT="$output_file"
# Run the script
local result=0
if [ -n "$range" ]; then
bash "$SCRIPT" "$version" "$range" &>/dev/null || result=$?
else
bash "$SCRIPT" "$version" &>/dev/null || result=$?
fi
# Check if script succeeded when it should
if [ $result -ne 0 ]; then
echo -e "${RED}${NC} $test_name (script failed with exit code $result)"
TESTS_FAILED=$((TESTS_FAILED + 1))
unset GITHUB_OUTPUT
return
fi
# Parse the output file
local actual_version=""
local actual_is_prerelease=""
local actual_range_satisfied=""
if [ -f "$output_file" ]; then
while IFS='=' read -r key value; do
case "$key" in
version)
actual_version="$value"
;;
is_prerelease)
actual_is_prerelease="$value"
;;
range_satisfied)
actual_range_satisfied="$value"
;;
esac
done < "$output_file"
fi
# Validate outputs
local test_passed=true
local error_msg=""
if [ "$actual_version" != "$expected_version" ]; then
test_passed=false
error_msg="${error_msg}version: expected '$expected_version', got '$actual_version'; "
fi
if [ "$actual_is_prerelease" != "$expected_is_prerelease" ]; then
test_passed=false
error_msg="${error_msg}is_prerelease: expected '$expected_is_prerelease', got '$actual_is_prerelease'; "
fi
if [ -n "$expected_range_satisfied" ] && [ "$actual_range_satisfied" != "$expected_range_satisfied" ]; then
test_passed=false
error_msg="${error_msg}range_satisfied: expected '$expected_range_satisfied', got '$actual_range_satisfied'; "
fi
# Report results
if [ "$test_passed" = true ]; then
echo -e "${GREEN}${NC} $test_name"
TESTS_PASSED=$((TESTS_PASSED + 1))
else
echo -e "${RED}${NC} $test_name"
echo -e " ${RED}${error_msg}${NC}"
TESTS_FAILED=$((TESTS_FAILED + 1))
fi
unset GITHUB_OUTPUT
}
# Print test section header
section() {
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
# Start tests
echo -e "${YELLOW}Testing GITHUB_OUTPUT functionality...${NC}"
# ============================================================================
# Test 1: Basic Output Without Range
# ============================================================================
section "Test 1: Basic Output Without Range"
test_github_output \
"Stable version output" \
"1.2.3" \
"" \
"1.2.3" \
"false"
test_github_output \
"Another stable version" \
"2.5.8" \
"" \
"2.5.8" \
"false"
test_github_output \
"Zero version output" \
"0.0.0" \
"" \
"0.0.0" \
"false"
test_github_output \
"Large version numbers" \
"100.200.300" \
"" \
"100.200.300" \
"false"
# ============================================================================
# Test 2: Pre-release Version Output
# ============================================================================
section "Test 2: Pre-release Version Output"
test_github_output \
"Pre-release alpha" \
"1.0.0-alpha" \
"" \
"1.0.0-alpha" \
"true"
test_github_output \
"Pre-release beta" \
"2.0.0-beta.1" \
"" \
"2.0.0-beta.1" \
"true"
test_github_output \
"Pre-release rc" \
"3.1.0-rc.2" \
"" \
"3.1.0-rc.2" \
"true"
test_github_output \
"Complex pre-release" \
"1.0.0-alpha.beta.1" \
"" \
"1.0.0-alpha.beta.1" \
"true"
# ============================================================================
# Test 3: Build Metadata (should be included in version output)
# ============================================================================
section "Test 3: Build Metadata in Output"
test_github_output \
"Stable with build metadata" \
"1.2.3+build.123" \
"" \
"1.2.3+build.123" \
"false"
test_github_output \
"Pre-release with build metadata" \
"1.0.0-beta+exp.sha.5114f85" \
"" \
"1.0.0-beta+exp.sha.5114f85" \
"true"
test_github_output \
"Build metadata only" \
"2.0.0+20130313144700" \
"" \
"2.0.0+20130313144700" \
"false"
# ============================================================================
# Test 4: Range Validation Output
# ============================================================================
section "Test 4: Range Validation Output"
test_github_output \
"Version satisfying >= range" \
"1.2.3" \
">=1.0.0" \
"1.2.3" \
"false" \
"true"
test_github_output \
"Version satisfying caret range" \
"2.5.0" \
"^2.0.0" \
"2.5.0" \
"false" \
"true"
test_github_output \
"Version satisfying tilde range" \
"1.2.5" \
"~1.2.0" \
"1.2.5" \
"false" \
"true"
test_github_output \
"Version satisfying wildcard range" \
"1.5.2" \
"1.x" \
"1.5.2" \
"false" \
"true"
test_github_output \
"Version satisfying compound range" \
"3.1.4" \
">=1.0.0 <4.0.0" \
"3.1.4" \
"false" \
"true"
# ============================================================================
# Test 5: Pre-release with Range
# ============================================================================
section "Test 5: Pre-release with Range Output"
test_github_output \
"Pre-release satisfying range" \
"1.2.3-beta.1" \
">=1.0.0" \
"1.2.3-beta.1" \
"true" \
"true"
test_github_output \
"Pre-release with caret range" \
"2.0.1-alpha" \
"^2.0.0" \
"2.0.1-alpha" \
"true" \
"true"
# ============================================================================
# Test 6: Edge Cases
# ============================================================================
section "Test 6: Edge Cases"
test_github_output \
"Boundary version with >= range" \
"1.0.0" \
">=1.0.0" \
"1.0.0" \
"false" \
"true"
test_github_output \
"Zero version with range" \
"0.0.0" \
">=0.0.0" \
"0.0.0" \
"false" \
"true"
test_github_output \
"Complex version with complex range" \
"1.5.0" \
">1.0.0 <=2.0.0" \
"1.5.0" \
"false" \
"true"
# ============================================================================
# Test 7: Verify Output File Format
# ============================================================================
section "Test 7: Output File Format Validation"
# Test that the output file has the correct format
output_file="$TEMP_DIR/format_test.txt"
export GITHUB_OUTPUT="$output_file"
bash "$SCRIPT" "1.2.3" ">=1.0.0" &>/dev/null
if [ -f "$output_file" ]; then
TESTS_RUN=$((TESTS_RUN + 1))
# Check that file contains key=value format
format_valid=true
while IFS= read -r line; do
if ! echo "$line" | grep -q '^[a-z_]*=.*$'; then
format_valid=false
break
fi
done < "$output_file"
if [ "$format_valid" = true ]; then
echo -e "${GREEN}${NC} Output file format is valid (key=value)"
TESTS_PASSED=$((TESTS_PASSED + 1))
else
echo -e "${RED}${NC} Output file format is invalid"
TESTS_FAILED=$((TESTS_FAILED + 1))
fi
# Check that file has exactly 3 lines when range is provided
TESTS_RUN=$((TESTS_RUN + 1))
line_count=$(wc -l < "$output_file")
if [ "$line_count" -eq 3 ]; then
echo -e "${GREEN}${NC} Output has correct number of lines (3) with range"
TESTS_PASSED=$((TESTS_PASSED + 1))
else
echo -e "${RED}${NC} Output has $line_count lines, expected 3"
TESTS_FAILED=$((TESTS_FAILED + 1))
fi
else
echo -e "${RED}${NC} Output file was not created"
TESTS_FAILED=$((TESTS_FAILED + 2))
TESTS_RUN=$((TESTS_RUN + 2))
fi
unset GITHUB_OUTPUT
# Test without range (should have 2 lines)
output_file="$TEMP_DIR/format_test_no_range.txt"
export GITHUB_OUTPUT="$output_file"
bash "$SCRIPT" "1.2.3" &>/dev/null
if [ -f "$output_file" ]; then
TESTS_RUN=$((TESTS_RUN + 1))
line_count=$(wc -l < "$output_file")
if [ "$line_count" -eq 2 ]; then
echo -e "${GREEN}${NC} Output has correct number of lines (2) without range"
TESTS_PASSED=$((TESTS_PASSED + 1))
else
echo -e "${RED}${NC} Output has $line_count lines, expected 2"
TESTS_FAILED=$((TESTS_FAILED + 1))
fi
fi
unset GITHUB_OUTPUT
# ============================================================================
# Test 8: No Output When GITHUB_OUTPUT Not Set
# ============================================================================
section "Test 8: No Output When GITHUB_OUTPUT Not Set"
TESTS_RUN=$((TESTS_RUN + 1))
# Make sure GITHUB_OUTPUT is not set
unset GITHUB_OUTPUT
# Run script
bash "$SCRIPT" "1.2.3" &>/dev/null
# There should be no github_output file created in current directory
if [ ! -f "github_output" ] && [ ! -f "${GITHUB_OUTPUT:-}" ]; then
echo -e "${GREEN}${NC} No output file created when GITHUB_OUTPUT not set"
TESTS_PASSED=$((TESTS_PASSED + 1))
else
echo -e "${RED}${NC} Output file was created when GITHUB_OUTPUT not set"
TESTS_FAILED=$((TESTS_FAILED + 1))
fi
# ============================================================================
# Test Results Summary
# ============================================================================
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}Test Results Summary${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "Total tests run: ${YELLOW}$TESTS_RUN${NC}"
echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}"
echo -e "Tests failed: ${RED}$TESTS_FAILED${NC}"
echo ""
if [ $TESTS_FAILED -eq 0 ]; then
echo -e "${GREEN}✓ All GITHUB_OUTPUT tests passed!${NC}"
exit 0
else
echo -e "${RED}✗ Some GITHUB_OUTPUT tests failed${NC}"
exit 1
fi

286
.github/scripts/test/test_validate_semver.sh vendored Executable file
View File

@@ -0,0 +1,286 @@
#!/usr/bin/env bash
set -euo pipefail
# Test script for validate_semver.sh
# Usage: ./test_validate_semver.sh
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Get the directory of this test script
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_DIR="$(dirname "$TEST_DIR")"
SCRIPT="$SCRIPT_DIR/validate_semver.sh"
# Test counters
TESTS_RUN=0
TESTS_PASSED=0
TESTS_FAILED=0
# Test helper functions
run_test() {
local test_name="$1"
local version="$2"
local range="${3:-}"
local should_pass="$4"
TESTS_RUN=$((TESTS_RUN + 1))
local result=0
if [ -n "$range" ]; then
bash "$SCRIPT" "$version" "$range" &>/dev/null || result=$?
else
bash "$SCRIPT" "$version" &>/dev/null || result=$?
fi
if [ "$should_pass" = "true" ]; then
if [ $result -eq 0 ]; then
echo -e "${GREEN}${NC} $test_name"
TESTS_PASSED=$((TESTS_PASSED + 1))
else
echo -e "${RED}${NC} $test_name (expected pass, got fail)"
TESTS_FAILED=$((TESTS_FAILED + 1))
fi
else
if [ $result -ne 0 ]; then
echo -e "${GREEN}${NC} $test_name"
TESTS_PASSED=$((TESTS_PASSED + 1))
else
echo -e "${RED}${NC} $test_name (expected fail, got pass)"
TESTS_FAILED=$((TESTS_FAILED + 1))
fi
fi
}
# Print test section header
section() {
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
# Start tests
echo -e "${YELLOW}Running validate_semver.sh tests...${NC}"
# ============================================================================
# Test 1: Basic SemVer Format Validation
# ============================================================================
section "Test 1: Basic SemVer Format Validation"
run_test "Valid stable version: 1.2.3" "1.2.3" "" "true"
run_test "Valid stable version: 0.0.0" "0.0.0" "" "true"
run_test "Valid stable version: 10.20.30" "10.20.30" "" "true"
run_test "Valid pre-release: 1.0.0-alpha" "1.0.0-alpha" "" "true"
run_test "Valid pre-release: 1.0.0-alpha.1" "1.0.0-alpha.1" "" "true"
run_test "Valid pre-release: 1.0.0-0.3.7" "1.0.0-0.3.7" "" "true"
run_test "Valid pre-release: 1.0.0-x.7.z.92" "1.0.0-x.7.z.92" "" "true"
run_test "Valid with build metadata: 1.0.0+20130313144700" "1.0.0+20130313144700" "" "true"
run_test "Valid with build metadata: 1.0.0-beta+exp.sha.5114f85" "1.0.0-beta+exp.sha.5114f85" "" "true"
run_test "Valid complex: 1.0.0-alpha.beta+build.1" "1.0.0-alpha.beta+build.1" "" "true"
run_test "Invalid: missing patch" "1.2" "" "false"
run_test "Invalid: missing minor and patch" "1" "" "false"
run_test "Invalid: non-numeric major" "v1.2.3" "" "false"
run_test "Invalid: leading zeros" "01.2.3" "" "false"
run_test "Invalid: leading zeros in minor" "1.02.3" "" "false"
run_test "Invalid: leading zeros in patch" "1.2.03" "" "false"
run_test "Invalid: empty string" "" "" "false"
run_test "Invalid: random text" "not-a-version" "" "false"
# ============================================================================
# Test 2: Comparison Operators
# ============================================================================
section "Test 2: Comparison Operators (>=, >, <=, <, =)"
# Greater than or equal (>=)
run_test "1.2.3 >= 1.0.0" "1.2.3" ">=1.0.0" "true"
run_test "1.2.3 >= 1.2.3" "1.2.3" ">=1.2.3" "true"
run_test "1.2.3 >= 2.0.0" "1.2.3" ">=2.0.0" "false"
# Greater than (>)
run_test "2.0.0 > 1.9.9" "2.0.0" ">1.9.9" "true"
run_test "1.2.3 > 1.2.3" "1.2.3" ">1.2.3" "false"
run_test "1.0.0 > 2.0.0" "1.0.0" ">2.0.0" "false"
# Less than or equal (<=)
run_test "1.0.0 <= 2.0.0" "1.0.0" "<=2.0.0" "true"
run_test "1.2.3 <= 1.2.3" "1.2.3" "<=1.2.3" "true"
run_test "3.0.0 <= 2.0.0" "3.0.0" "<=2.0.0" "false"
# Less than (<)
run_test "1.0.0 < 2.0.0" "1.0.0" "<2.0.0" "true"
run_test "1.2.3 < 1.2.3" "1.2.3" "<1.2.3" "false"
run_test "3.0.0 < 2.0.0" "3.0.0" "<2.0.0" "false"
# Equal (=)
run_test "1.2.3 = 1.2.3" "1.2.3" "=1.2.3" "true"
run_test "1.2.3 = 1.2.4" "1.2.3" "=1.2.4" "false"
# ============================================================================
# Test 3: Caret Ranges (^)
# ============================================================================
section "Test 3: Caret Ranges (^)"
# Standard caret ranges
run_test "2.5.0 satisfies ^2.0.0" "2.5.0" "^2.0.0" "true"
run_test "2.0.0 satisfies ^2.0.0" "2.0.0" "^2.0.0" "true"
run_test "2.9.9 satisfies ^2.0.0" "2.9.9" "^2.0.0" "true"
run_test "3.0.0 does not satisfy ^2.0.0" "3.0.0" "^2.0.0" "false"
run_test "1.9.9 does not satisfy ^2.0.0" "1.9.9" "^2.0.0" "false"
# Caret with 0.x versions
run_test "0.2.5 satisfies ^0.2.3" "0.2.5" "^0.2.3" "true"
run_test "0.2.3 satisfies ^0.2.3" "0.2.3" "^0.2.3" "true"
run_test "0.3.0 does not satisfy ^0.2.3" "0.3.0" "^0.2.3" "false"
run_test "0.2.2 does not satisfy ^0.2.3" "0.2.2" "^0.2.3" "false"
# Caret with 0.0.x versions
run_test "0.0.3 satisfies ^0.0.3" "0.0.3" "^0.0.3" "true"
run_test "0.0.4 does not satisfy ^0.0.3" "0.0.4" "^0.0.3" "false"
# Caret with pre-release
run_test "2.0.0-rc.1 does not satisfy ^2.0.0" "2.0.0-rc.1" "^2.0.0" "false"
run_test "2.0.1 satisfies ^2.0.0" "2.0.1" "^2.0.0" "true"
# ============================================================================
# Test 4: Tilde Ranges (~)
# ============================================================================
section "Test 4: Tilde Ranges (~)"
# Standard tilde ranges
run_test "1.2.9 satisfies ~1.2.0" "1.2.9" "~1.2.0" "true"
run_test "1.2.0 satisfies ~1.2.0" "1.2.0" "~1.2.0" "true"
run_test "1.3.0 does not satisfy ~1.2.0" "1.3.0" "~1.2.0" "false"
run_test "1.1.9 does not satisfy ~1.2.0" "1.1.9" "~1.2.0" "false"
# Tilde with different versions
run_test "2.5.8 satisfies ~2.5.0" "2.5.8" "~2.5.0" "true"
run_test "2.6.0 does not satisfy ~2.5.0" "2.6.0" "~2.5.0" "false"
run_test "0.1.5 satisfies ~0.1.0" "0.1.5" "~0.1.0" "true"
run_test "0.2.0 does not satisfy ~0.1.0" "0.2.0" "~0.1.0" "false"
# ============================================================================
# Test 5: Wildcard Ranges (x, X, *)
# ============================================================================
section "Test 5: Wildcard Ranges (x, X, *)"
# Major wildcard
run_test "1.5.2 satisfies 1.x" "1.5.2" "1.x" "true"
run_test "1.0.0 satisfies 1.x" "1.0.0" "1.x" "true"
run_test "1.9.9 satisfies 1.x" "1.9.9" "1.x" "true"
run_test "2.0.0 does not satisfy 1.x" "2.0.0" "1.x" "false"
# Major wildcard with X
run_test "2.3.4 satisfies 2.X" "2.3.4" "2.X" "true"
run_test "3.0.0 does not satisfy 2.X" "3.0.0" "2.X" "false"
# Major wildcard with *
run_test "3.1.2 satisfies 3.*" "3.1.2" "3.*" "true"
run_test "4.0.0 does not satisfy 3.*" "4.0.0" "3.*" "false"
# Minor wildcard
run_test "1.5.2 satisfies 1.5.x" "1.5.2" "1.5.x" "true"
run_test "1.5.0 satisfies 1.5.x" "1.5.0" "1.5.x" "true"
run_test "1.6.0 does not satisfy 1.5.x" "1.6.0" "1.5.x" "false"
# Full wildcard
run_test "1.2.3 satisfies 1.x.x" "1.2.3" "1.x.x" "true"
run_test "1.5.8 satisfies 1.x.x" "1.5.8" "1.x.x" "true"
# Wildcard with prerelease
run_test "1.5.2-beta satisfies 1.x" "1.5.2-beta" "1.x" "true"
# ============================================================================
# Test 6: Compound Ranges
# ============================================================================
section "Test 6: Compound Ranges (Multiple Constraints)"
# AND ranges
run_test "1.5.0 satisfies >=1.0.0 <2.0.0" "1.5.0" ">=1.0.0 <2.0.0" "true"
run_test "2.0.0 does not satisfy >=1.0.0 <2.0.0" "2.0.0" ">=1.0.0 <2.0.0" "false"
run_test "0.9.9 does not satisfy >=1.0.0 <2.0.0" "0.9.9" ">=1.0.0 <2.0.0" "false"
run_test "3.1.4 satisfies >=1.0.0 <4.0.0" "3.1.4" ">=1.0.0 <4.0.0" "true"
run_test "4.0.0 does not satisfy >=1.0.0 <4.0.0" "4.0.0" ">=1.0.0 <4.0.0" "false"
run_test "1.5.0 satisfies >1.0.0 <=2.0.0" "1.5.0" ">1.0.0 <=2.0.0" "true"
run_test "1.0.0 does not satisfy >1.0.0 <=2.0.0" "1.0.0" ">1.0.0 <=2.0.0" "false"
run_test "2.0.0 satisfies >1.0.0 <=2.0.0" "2.0.0" ">1.0.0 <=2.0.0" "true"
# Multiple constraints
run_test "2.5.0 satisfies >=2.0.0 <=3.0.0" "2.5.0" ">=2.0.0 <=3.0.0" "true"
run_test "2.5.0 satisfies >2.0.0 <3.0.0" "2.5.0" ">2.0.0 <3.0.0" "true"
# ============================================================================
# Test 7: Pre-release Version Comparisons
# ============================================================================
section "Test 7: Pre-release Version Comparisons"
# Pre-release comparisons
run_test "2.0.0-alpha satisfies >=2.0.0-alpha" "2.0.0-alpha" ">=2.0.0-alpha" "true"
run_test "2.0.0-beta satisfies >2.0.0-alpha" "2.0.0-beta" ">2.0.0-alpha" "true"
run_test "2.0.0-alpha satisfies <2.0.0" "2.0.0-alpha" "<2.0.0" "true"
run_test "2.0.0 satisfies >2.0.0-alpha" "2.0.0" ">2.0.0-alpha" "true"
run_test "1.0.0-rc.1 satisfies >=1.0.0-rc.1" "1.0.0-rc.1" ">=1.0.0-rc.1" "true"
run_test "1.0.0-rc.2 satisfies >1.0.0-rc.1" "1.0.0-rc.2" ">1.0.0-rc.1" "true"
# Pre-release with ranges
run_test "1.2.3-beta.1 satisfies >=1.0.0" "1.2.3-beta.1" ">=1.0.0" "true"
run_test "1.2.3-beta.1 satisfies >=1.2.3-alpha" "1.2.3-beta.1" ">=1.2.3-alpha" "true"
# ============================================================================
# Test 8: Build Metadata
# ============================================================================
section "Test 8: Build Metadata (should be ignored in comparisons)"
run_test "1.2.3+build.1 = 1.2.3" "1.2.3+build.1" "=1.2.3" "true"
run_test "1.2.3+build.1 = 1.2.3+build.2" "1.2.3+build.1" "=1.2.3+build.2" "true"
run_test "1.2.3+build satisfies >=1.2.0" "1.2.3+build" ">=1.2.0" "true"
run_test "1.0.0-beta+exp.sha.5114f85 satisfies <1.0.0" "1.0.0-beta+exp.sha.5114f85" "<1.0.0" "true"
# ============================================================================
# Test 9: Edge Cases
# ============================================================================
section "Test 9: Edge Cases"
# Boundary conditions
run_test "1.0.0 satisfies >=1.0.0" "1.0.0" ">=1.0.0" "true"
run_test "1.0.0 does not satisfy >1.0.0" "1.0.0" ">1.0.0" "false"
run_test "1.0.0 satisfies <=1.0.0" "1.0.0" "<=1.0.0" "true"
run_test "1.0.0 does not satisfy <1.0.0" "1.0.0" "<1.0.0" "false"
# Large version numbers
run_test "100.200.300 satisfies >=1.0.0" "100.200.300" ">=1.0.0" "true"
run_test "999.999.999 satisfies ^999.0.0" "999.999.999" "^999.0.0" "true"
# Zero versions
run_test "0.0.0 satisfies >=0.0.0" "0.0.0" ">=0.0.0" "true"
run_test "0.1.0 satisfies >0.0.0" "0.1.0" ">0.0.0" "true"
run_test "0.0.1 satisfies ^0.0.1" "0.0.1" "^0.0.1" "true"
# ============================================================================
# Test Results Summary
# ============================================================================
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}Test Results Summary${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "Total tests run: ${YELLOW}$TESTS_RUN${NC}"
echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}"
echo -e "Tests failed: ${RED}$TESTS_FAILED${NC}"
echo ""
if [ $TESTS_FAILED -eq 0 ]; then
echo -e "${GREEN}✓ All tests passed!${NC}"
exit 0
else
echo -e "${RED}✗ Some tests failed${NC}"
exit 1
fi