tests: harden shell tests; add header/CORS assertions; nonzero exits; bump to v0.4.1
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
colin 2025-08-27 19:26:59 -04:00
parent 4276c0bdf1
commit 5d60f54f9c
6 changed files with 220 additions and 298 deletions

View File

@ -1 +1 @@
0.4.0 0.4.1

View File

@ -17,7 +17,7 @@ services:
- POSTGRES_DB=ploughshares - POSTGRES_DB=ploughshares
- POSTGRES_USER=ploughshares - POSTGRES_USER=ploughshares
- POSTGRES_PASSWORD=ploughshares_password - POSTGRES_PASSWORD=ploughshares_password
- APP_VERSION=0.4.0 - APP_VERSION=0.4.1
- APP_ENV=development - APP_ENV=development
depends_on: depends_on:
db: db:

View File

@ -1,5 +1,7 @@
#!/bin/bash #!/bin/bash
set -Eeuo pipefail
# Colors for better readability # Colors for better readability
GREEN='\033[0;32m' GREEN='\033[0;32m'
RED='\033[0;31m' RED='\033[0;31m'
@ -7,6 +9,10 @@ YELLOW='\033[0;33m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
FAILURES=0
fail() { echo -e "${RED}$1${NC}"; FAILURES=$((FAILURES+1)); }
pass() { echo -e "${GREEN}$1${NC}"; }
# Base URLs to test # Base URLs to test
DEV_URL="http://localhost:5005" DEV_URL="http://localhost:5005"
PROD_URL="https://ploughshares.nixc.us" PROD_URL="https://ploughshares.nixc.us"
@ -17,94 +23,58 @@ URL=${1:-$DEV_URL}
echo -e "${BLUE}Testing API endpoints with curl for ${URL}${NC}" echo -e "${BLUE}Testing API endpoints with curl for ${URL}${NC}"
echo "==================================================" echo "=================================================="
# Function to test API endpoint with different curl options assert_status_and_json_field() {
test_api() { local method=$1; shift
local endpoint=$1 local url=$1; shift
local method=$2 local data=${1:-}; shift || true
local description=$3 local headers=${1:-}; shift || true
local headers=$4 local expected_status=${1:-200}; shift || true
local data=$5 local json_field=${1:-}; shift || true
local full_url="${URL}${endpoint}"
echo -e "\n${YELLOW}Test: ${description}${NC}" local tmp
echo "------------------------------------------------" tmp=$(mktemp)
local cmd="curl -s -o $tmp -w \"%{http_code}\" -X $method"
# Build curl command based on parameters
cmd="curl -s"
# Add method if specified
if [ -n "$method" ]; then
cmd="$cmd -X $method"
fi
# Add headers if specified
if [ -n "$headers" ]; then if [ -n "$headers" ]; then
cmd="$cmd $headers" cmd="$cmd $headers"
fi fi
# Add data if specified
if [ -n "$data" ]; then if [ -n "$data" ]; then
cmd="$cmd -d '$data'" cmd="$cmd -d '$data'"
fi fi
cmd="$cmd \"$url\""
# Add URL
cmd="$cmd \"$full_url\""
# Show the command being executed
echo -e "${BLUE}Command:${NC} $cmd" echo -e "${BLUE}Command:${NC} $cmd"
# shellcheck disable=SC2086
status=$(eval $cmd)
echo -e "${BLUE}Status:${NC} $status"
echo -e "${BLUE}Body:${NC}"; cat "$tmp" | jq 2>/dev/null || cat "$tmp"
# Execute the command if [ "$status" != "$expected_status" ]; then
echo -e "${BLUE}Response:${NC}" fail "$method $url expected $expected_status, got $status"
eval $cmd | jq 2>/dev/null || echo "Response is not valid JSON" rm -f "$tmp"
return
echo "------------------------------------------------" fi
if [ -n "$json_field" ] && ! jq -e ".${json_field} // empty" >/dev/null 2>&1 <"$tmp"; then
fail "$method $url response missing field: $json_field"
rm -f "$tmp"
return
fi
pass "$method $url returned $expected_status${json_field:+ and contains $json_field}"
rm -f "$tmp"
} }
# Test 1: Basic GET request to API test endpoint # Tests that do not mutate state
test_api "/api/test" "GET" "Basic GET request to API test endpoint" "" "" assert_status_and_json_field GET "${URL}/api/test" "" "-H \"Accept: application/json\"" 200 status
assert_status_and_json_field OPTIONS "${URL}/api/test" "" "-H \"Origin: http://example.com\"" 200
# Test 2: GET request with Accept header # Mutating tests guarded by env var
test_api "/api/test" "GET" "GET request with Accept: application/json header" "-H \"Accept: application/json\"" "" if [[ "${ALLOW_MUTATING_TESTS:-0}" == "1" ]]; then
assert_status_and_json_field POST "${URL}/api/transaction" '{"transaction_type":"test","company_division":"div","recipient":"Test Corp"}' "-H \"Content-Type: application/json\" -H \"Accept: application/json\"" 201 transaction_id
else
echo -e "${YELLOW}Skipping POST/PUT/DELETE tests (set ALLOW_MUTATING_TESTS=1 to enable).${NC}"
fi
# Test 3: OPTIONS request (CORS preflight) if [ "$FAILURES" -gt 0 ]; then
test_api "/api/test" "OPTIONS" "OPTIONS request (CORS preflight)" "-H \"Origin: http://example.com\"" "" echo -e "${RED}API curl tests failed: $FAILURES issue(s).${NC}"; exit 1
else
# Test 4: POST request with JSON data echo -e "${GREEN}All API curl tests passed.${NC}"
test_api "/api/transactions" "POST" "POST request with JSON data" "-H \"Content-Type: application/json\"" "{\"test\": \"data\"}" fi
# Test 5: GET request with custom User-Agent
test_api "/api/test" "GET" "GET request with custom User-Agent" "-H \"User-Agent: CustomClient/1.0\"" ""
# Test 6: GET request with Authorization header
test_api "/api/test" "GET" "GET request with Authorization header" "-H \"Authorization: Bearer test-token\"" ""
# Test 7: POST request with form data
test_api "/api/transactions" "POST" "POST request with form data" "-H \"Content-Type: application/x-www-form-urlencoded\"" "test=data"
# Test 8: GET request with multiple custom headers
test_api "/api/test" "GET" "GET request with multiple custom headers" "-H \"Accept: application/json\" -H \"X-Custom-Header: test\"" ""
echo -e "\n${BLUE}API curl tests completed for ${URL}${NC}"
# Now test the API with a more complex curl command that simulates a real client
echo -e "\n${YELLOW}Simulating a real API client with complex curl command${NC}"
echo "------------------------------------------------"
echo -e "${BLUE}Command:${NC}"
echo "curl -v -X POST \"${URL}/api/transactions\" \\"
echo " -H \"Content-Type: application/json\" \\"
echo " -H \"Accept: application/json\" \\"
echo " -H \"User-Agent: APIClient/1.0\" \\"
echo " -H \"Authorization: Bearer test-token\" \\"
echo " -d '{\"transaction_type\":\"test\",\"amount\":100,\"recipient\":\"Test Corp\"}'"
echo -e "\n${BLUE}Response:${NC}"
curl -v -X POST "${URL}/api/transactions" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "User-Agent: APIClient/1.0" \
-H "Authorization: Bearer test-token" \
-d '{"transaction_type":"test","amount":100,"recipient":"Test Corp"}' 2>&1
echo -e "\n------------------------------------------------"
echo -e "${GREEN}All tests completed.${NC}"

View File

@ -1,138 +1,72 @@
#!/bin/bash #!/bin/bash
# Colors for better readability set -Eeuo pipefail
GREEN='\033[0;32m' GREEN='\033[0;32m'
RED='\033[0;31m' RED='\033[0;31m'
YELLOW='\033[0;33m' YELLOW='\033[0;33m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
NC='\033[0m' # No Color NC='\033[0m'
FAILURES=0
fail() { echo -e "${RED}$1${NC}"; FAILURES=$((FAILURES+1)); }
pass() { echo -e "${GREEN}$1${NC}"; }
require_header() {
local headers=$1; local name=$2; local pattern=${3:-}
if ! echo "$headers" | grep -qi "^$name:"; then fail "$name header not found"; return; fi
if [ -n "$pattern" ] && ! echo "$headers" | grep -i "^$name:" | grep -qiE "$pattern"; then fail "$name does not match expected pattern: $pattern"; return; fi
pass "$name present"
}
require_header_absent() {
local headers=$1; local name=$2
if echo "$headers" | grep -qi "^$name:"; then fail "$name should be absent"; else pass "$name absent as expected"; fi
}
assert_http_status() {
local url=$1; local expected=$2
local code; code=$(curl -s -o /dev/null -w "%{http_code}" -I "$url")
if [ "$code" != "$expected" ]; then fail "HTTP status $url expected $expected, got $code"; else pass "HTTP $expected for $url"; fi
}
# Base URLs to test
DEV_URL="http://localhost:5005" DEV_URL="http://localhost:5005"
PROD_URL="https://ploughshares.nixc.us" PROD_URL="https://ploughshares.nixc.us"
# Default to development URL unless specified
URL=${1:-$DEV_URL} URL=${1:-$DEV_URL}
echo -e "${BLUE}Testing API Security Headers for ${URL}${NC}" echo -e "${BLUE}Testing API Security Headers for ${URL}${NC}"
echo "==================================================" echo "=================================================="
# Function to test an endpoint's headers test_api_headers() {
test_headers() { local endpoint=$1; local description=$2; local full_url="${URL}${endpoint}"
local endpoint=$1 echo -e "\n${YELLOW}${description} (${endpoint})${NC}"
local description=$2
local full_url="${URL}${endpoint}"
echo -e "\n${YELLOW}Testing headers for ${description} (${endpoint})${NC}"
echo "------------------------------------------------" echo "------------------------------------------------"
assert_http_status "$full_url" 200
headers=$(curl -s -I "$full_url")
echo -e "${BLUE}Headers:${NC}"; echo "$headers" | grep -v "Date:" | grep -v "Server:"
# Get headers only with curl require_header "$headers" "Content-Security-Policy"
headers=$(curl -s -I "${full_url}") require_header "$headers" "Access-Control-Allow-Origin" "\*"
require_header "$headers" "Access-Control-Allow-Methods"
# Display all headers for reference require_header "$headers" "Access-Control-Allow-Headers"
echo -e "${BLUE}All Headers:${NC}" require_header "$headers" "Cross-Origin-Resource-Policy" "cross-origin"
echo "$headers" | grep -v "Date:" | grep -v "Server:" require_header_absent "$headers" "Cross-Origin-Embedder-Policy"
echo -e "\n${BLUE}Security Header Analysis:${NC}"
# Check for Content-Security-Policy
if echo "$headers" | grep -q "Content-Security-Policy"; then
csp=$(echo "$headers" | grep "Content-Security-Policy" | sed "s/Content-Security-Policy: //")
echo -e "${GREEN}✓ Content-Security-Policy:${NC}"
# Check for unsafe-inline and unsafe-eval in script-src
if echo "$csp" | grep -q "script-src.*'unsafe-inline'"; then
echo -e " ${YELLOW}⚠ script-src contains 'unsafe-inline' - needed for API clients${NC}"
else
echo -e " ${RED}✗ script-src missing 'unsafe-inline' - may cause issues with some clients${NC}"
fi
if echo "$csp" | grep -q "script-src.*'unsafe-eval'"; then
echo -e " ${YELLOW}⚠ script-src contains 'unsafe-eval' - needed for API clients${NC}"
else
echo -e " ${RED}✗ script-src missing 'unsafe-eval' - may cause issues with some clients${NC}"
fi
# Check for connect-src
if echo "$csp" | grep -q "connect-src.*\\*"; then
echo -e " ${GREEN}✓ connect-src includes * - good for API access${NC}"
else
echo -e " ${RED}✗ connect-src restrictive - may block API access${NC}"
fi
else
echo -e "${RED}✗ Content-Security-Policy header not found${NC}"
fi
# Check for CORS headers for API endpoints
if [[ "$endpoint" == "/api/"* ]]; then
echo -e "\n${BLUE}CORS Headers (critical for API):${NC}"
if echo "$headers" | grep -q "Access-Control-Allow-Origin"; then
origin=$(echo "$headers" | grep "Access-Control-Allow-Origin" | sed "s/Access-Control-Allow-Origin: //")
echo -e "${GREEN}✓ Access-Control-Allow-Origin: $origin${NC}"
else
echo -e "${RED}✗ Access-Control-Allow-Origin header not found - CORS will fail${NC}"
fi
if echo "$headers" | grep -q "Access-Control-Allow-Methods"; then
methods=$(echo "$headers" | grep "Access-Control-Allow-Methods" | sed "s/Access-Control-Allow-Methods: //")
echo -e "${GREEN}✓ Access-Control-Allow-Methods: $methods${NC}"
else
echo -e "${RED}✗ Access-Control-Allow-Methods header not found - CORS will fail${NC}"
fi
if echo "$headers" | grep -q "Access-Control-Allow-Headers"; then
allowed_headers=$(echo "$headers" | grep "Access-Control-Allow-Headers" | sed "s/Access-Control-Allow-Headers: //")
echo -e "${GREEN}✓ Access-Control-Allow-Headers: $allowed_headers${NC}"
else
echo -e "${RED}✗ Access-Control-Allow-Headers header not found - CORS will fail${NC}"
fi
fi
# Check for Cross-Origin-Resource-Policy
if echo "$headers" | grep -q "Cross-Origin-Resource-Policy"; then
corp=$(echo "$headers" | grep "Cross-Origin-Resource-Policy" | sed "s/Cross-Origin-Resource-Policy: //")
echo -e "\n${BLUE}Resource Policy:${NC}"
if [[ "$endpoint" == "/api/"* ]]; then
if [[ "$corp" == "cross-origin" ]]; then
echo -e "${GREEN}✓ Cross-Origin-Resource-Policy: $corp - correct for API${NC}"
else
echo -e "${RED}✗ Cross-Origin-Resource-Policy: $corp - should be 'cross-origin' for API endpoints${NC}"
fi
else
if [[ "$corp" == "same-origin" ]]; then
echo -e "${GREEN}✓ Cross-Origin-Resource-Policy: $corp - correct for UI${NC}"
else
echo -e "${YELLOW}⚠ Cross-Origin-Resource-Policy: $corp - should be 'same-origin' for UI endpoints${NC}"
fi
fi
else
echo -e "${YELLOW}⚠ Cross-Origin-Resource-Policy header not found${NC}"
fi
echo "------------------------------------------------"
} }
# Test UI endpoint test_api_headers "/api/test" "API test endpoint headers"
test_headers "/" "Main UI page"
# Test API endpoints echo -e "\n${YELLOW}OPTIONS preflight for /api/test${NC}"
test_headers "/api/test" "API test endpoint" echo "------------------------------------------------"
preflight_code=$(curl -s -o /dev/null -w "%{http_code}" -X OPTIONS -I "${URL}/api/test")
if [ "$preflight_code" != "200" ]; then fail "OPTIONS /api/test expected 200, got $preflight_code"; else pass "OPTIONS /api/test returns 200"; fi
preflight_headers=$(curl -s -X OPTIONS -I "${URL}/api/test")
require_header "$preflight_headers" "Access-Control-Allow-Origin" "\*"
require_header "$preflight_headers" "Access-Control-Allow-Methods"
require_header "$preflight_headers" "Access-Control-Allow-Headers"
# Test OPTIONS request for CORS preflight if [ "$FAILURES" -gt 0 ]; then
echo -e "\n${YELLOW}Testing OPTIONS request for CORS preflight${NC}" echo -e "${RED}API header tests failed: $FAILURES issue(s).${NC}"; exit 1
echo "------------------------------------------------" else
curl -s -X OPTIONS -H "Origin: http://example.com" -I "${URL}/api/test" | grep -v "Date:" | grep -v "Server:" echo -e "${GREEN}All API header tests passed.${NC}"
echo "------------------------------------------------" fi
# Test with a real-world API client simulation
echo -e "\n${YELLOW}Testing with a simulated API client${NC}"
echo "------------------------------------------------"
echo -e "${BLUE}Command:${NC} curl -s -X GET -H \"Origin: http://example.com\" -H \"Accept: application/json\" \"${URL}/api/test\""
response=$(curl -s -X GET -H "Origin: http://example.com" -H "Accept: application/json" "${URL}/api/test")
echo -e "${BLUE}Response:${NC}"
echo "$response" | jq 2>/dev/null || echo "$response"
echo "------------------------------------------------"
echo -e "\n${GREEN}All header tests completed.${NC}"

View File

@ -1,5 +1,7 @@
#!/bin/bash #!/bin/bash
set -Eeuo pipefail
# Colors for better readability # Colors for better readability
GREEN='\033[0;32m' GREEN='\033[0;32m'
RED='\033[0;31m' RED='\033[0;31m'
@ -7,6 +9,56 @@ YELLOW='\033[0;33m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
FAILURES=0
fail() {
echo -e "${RED}$1${NC}"
FAILURES=$((FAILURES+1))
}
pass() {
echo -e "${GREEN}$1${NC}"
}
require_header() {
local headers=$1
local name=$2
local pattern=${3:-}
if ! echo "$headers" | grep -qi "^$name:"; then
fail "$name header not found"
return
fi
if [ -n "$pattern" ]; then
if ! echo "$headers" | grep -i "^$name:" | grep -qiE "$pattern"; then
fail "$name does not match expected pattern: $pattern"
return
fi
fi
pass "$name present"
}
require_header_absent() {
local headers=$1
local name=$2
if echo "$headers" | grep -qi "^$name:"; then
fail "$name should be absent"
else
pass "$name absent as expected"
fi
}
assert_http_status() {
local url=$1
local expected=$2
local code
code=$(curl -s -o /dev/null -w "%{http_code}" -I "$url")
if [ "$code" != "$expected" ]; then
fail "HTTP status for $url expected $expected, got $code"
else
pass "HTTP $expected for $url"
fi
}
# Base URLs to test # Base URLs to test
DEV_URL="http://localhost:5005" DEV_URL="http://localhost:5005"
PROD_URL="https://ploughshares.nixc.us" PROD_URL="https://ploughshares.nixc.us"
@ -17,107 +69,72 @@ URL=${1:-$DEV_URL}
echo -e "${BLUE}Testing headers for ${URL}${NC}" echo -e "${BLUE}Testing headers for ${URL}${NC}"
echo "==================================================" echo "=================================================="
# Function to test an endpoint test_headers() {
test_endpoint() {
local endpoint=$1 local endpoint=$1
local method=$2 local description=$2
local description=$3
local full_url="${URL}${endpoint}" local full_url="${URL}${endpoint}"
echo -e "\n${YELLOW}Testing ${method} ${endpoint}: ${description}${NC}" echo -e "\n${YELLOW}Testing headers for ${description} (${endpoint})${NC}"
echo "------------------------------------------------" echo "------------------------------------------------"
if [ "$method" == "HEAD" ]; then assert_http_status "$full_url" 200
# Use -I for HEAD requests
curl -s -I "${full_url}" | grep -v "Date:" | grep -v "Server:"
elif [ "$method" == "OPTIONS" ]; then
# Use -X OPTIONS for OPTIONS requests
curl -s -X OPTIONS "${full_url}" -I | grep -v "Date:" | grep -v "Server:"
elif [ "$method" == "GET" ]; then
# First check headers
echo -e "${BLUE}Headers:${NC}"
curl -s -I "${full_url}" | grep -v "Date:" | grep -v "Server:"
# Then check response body headers=$(curl -s -I "$full_url")
echo -e "\n${BLUE}Response:${NC}"
curl -s "${full_url}"
echo -e "\n"
else
echo "Unsupported method: ${method}"
fi
echo "------------------------------------------------" echo -e "${BLUE}All Headers:${NC}"
} echo "$headers" | grep -v "Date:" | grep -v "Server:"
# Function to check for specific security headers # Common expectations
check_security_headers() { require_header "$headers" "Content-Security-Policy"
local endpoint=$1 require_header "$headers" "Referrer-Policy"
local full_url="${URL}${endpoint}" require_header "$headers" "X-Frame-Options"
require_header "$headers" "X-Content-Type-Options"
echo -e "\n${YELLOW}Checking security headers for ${endpoint}${NC}"
echo "------------------------------------------------"
# Store headers in a variable
headers=$(curl -s -I "${full_url}")
# Check for important security headers
check_header "Content-Security-Policy" "$headers"
check_header "Cross-Origin-Resource-Policy" "$headers"
check_header "Cross-Origin-Embedder-Policy" "$headers"
check_header "Cross-Origin-Opener-Policy" "$headers"
check_header "X-Frame-Options" "$headers"
check_header "X-Content-Type-Options" "$headers"
check_header "X-XSS-Protection" "$headers"
check_header "Referrer-Policy" "$headers"
# For API endpoints, check CORS headers
if [[ "$endpoint" == "/api/"* ]]; then if [[ "$endpoint" == "/api/"* ]]; then
check_header "Access-Control-Allow-Origin" "$headers" # API expectations (CORS enabled, CORP cross-origin, COEP absent)
check_header "Access-Control-Allow-Methods" "$headers" require_header "$headers" "Access-Control-Allow-Origin" "\*"
check_header "Access-Control-Allow-Headers" "$headers" require_header "$headers" "Access-Control-Allow-Methods"
require_header "$headers" "Access-Control-Allow-Headers"
require_header "$headers" "Cross-Origin-Resource-Policy" "cross-origin"
require_header_absent "$headers" "Cross-Origin-Embedder-Policy"
else
# UI expectations (no CORS headers, CORP same-origin, COEP unsafe-none)
require_header "$headers" "Cross-Origin-Resource-Policy" "same-origin"
require_header "$headers" "Cross-Origin-Embedder-Policy" "unsafe-none"
if echo "$headers" | grep -qi "^Access-Control-Allow-Origin:"; then
fail "Access-Control-Allow-Origin should not be present on UI routes"
else
pass "No CORS headers on UI routes"
fi
fi fi
echo "------------------------------------------------" echo "------------------------------------------------"
} }
# Function to check for a specific header # Test UI endpoint
check_header() { test_headers "/" "Main UI page"
local header=$1
local headers=$2
if echo "$headers" | grep -q "$header"; then # Test API endpoint
header_value=$(echo "$headers" | grep "$header" | sed "s/$header: //") test_headers "/api/test" "API test endpoint"
echo -e "${GREEN}$header: $header_value${NC}"
else
echo -e "${RED}$header not found${NC}"
fi
}
# Test UI endpoints # OPTIONS preflight check
test_endpoint "/" "HEAD" "Main page headers" echo -e "\n${YELLOW}Testing OPTIONS request for CORS preflight${NC}"
check_security_headers "/"
# Test API endpoints
test_endpoint "/api/test" "HEAD" "API test endpoint headers"
check_security_headers "/api/test"
test_endpoint "/api/test" "GET" "API test endpoint response"
# Test OPTIONS request for CORS preflight
test_endpoint "/api/test" "OPTIONS" "API CORS preflight request"
# Test a POST request to API
echo -e "\n${YELLOW}Testing POST to /api/transactions${NC}"
echo "------------------------------------------------" echo "------------------------------------------------"
curl -s -X POST "${URL}/api/transactions" \ preflight_code=$(curl -s -o /dev/null -w "%{http_code}" -X OPTIONS -I "${URL}/api/test")
-H "Content-Type: application/json" \ if [ "$preflight_code" != "200" ]; then
-d '{"test": "data"}' | jq 2>/dev/null || echo "Response is not valid JSON" fail "OPTIONS /api/test expected 200, got $preflight_code"
echo -e "\n------------------------------------------------" else
pass "OPTIONS /api/test returns 200"
# Test with different Accept headers fi
echo -e "\n${YELLOW}Testing with Accept: application/json header${NC}" preflight_headers=$(curl -s -X OPTIONS -I "${URL}/api/test")
require_header "$preflight_headers" "Access-Control-Allow-Origin" "\*"
require_header "$preflight_headers" "Access-Control-Allow-Methods"
require_header "$preflight_headers" "Access-Control-Allow-Headers"
echo "------------------------------------------------" echo "------------------------------------------------"
curl -s -H "Accept: application/json" "${URL}/api/test" | jq 2>/dev/null || echo "Response is not valid JSON"
echo -e "\n------------------------------------------------"
echo -e "\n${BLUE}Tests completed for ${URL}${NC}" if [ "$FAILURES" -gt 0 ]; then
echo -e "${RED}Header tests failed: $FAILURES issue(s).${NC}"
exit 1
else
echo -e "${GREEN}All header tests passed.${NC}"
fi

View File

@ -5,3 +5,4 @@ Fri Jul 4 20:03:15 EDT 2025: Version changed from 0.2.0 to 0.2.1
Fri Jul 4 22:25:55 EDT 2025: Version changed from 0.2.1 to 0.3.0 Fri Jul 4 22:25:55 EDT 2025: Version changed from 0.2.1 to 0.3.0
Fri Jul 4 22:33:48 EDT 2025: Version changed from 0.3.0 to 0.3.1 Fri Jul 4 22:33:48 EDT 2025: Version changed from 0.3.0 to 0.3.1
Fri Jul 4 23:19:37 EDT 2025: Version changed from 0.3.1 to 0.4.0 Fri Jul 4 23:19:37 EDT 2025: Version changed from 0.3.1 to 0.4.0
Wed Aug 27 19:26:44 EDT 2025: Version changed from 0.4.0 to 0.4.1