Skip to content

Baseline spec introduction #182

Baseline spec introduction

Baseline spec introduction #182

Workflow file for this run

name: Examples CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
paths:
- 'examples/**'
- '.github/workflows/examples-ci.yml'
# Allow manual trigger
workflow_dispatch:
env:
GO_VERSION: '^1.25'
jobs:
validate-examples:
name: Validate Examples
runs-on: ubuntu-latest
strategy:
matrix:
example:
- basic-app
- reverse-proxy
- http-client
- advanced-logging
- multi-tenant-app
- instance-aware-db
- verbose-debug
- feature-flag-proxy
- testing-scenarios
- observer-pattern
- health-aware-reverse-proxy
- multi-engine-eventbus
- logmasker-example
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
cache: true
- name: Validate example structure
run: |
cd examples/${{ matrix.example }}
# Check required files exist
if [ ! -f "go.mod" ]; then
echo "❌ Missing go.mod in ${{ matrix.example }}"
exit 1
fi
if [ ! -f "config.yaml" ]; then
echo "❌ Missing config.yaml in ${{ matrix.example }}"
exit 1
fi
if [ ! -f "main.go" ] && [ ! -f "$(basename $(pwd)).go" ]; then
echo "❌ Missing main Go file in ${{ matrix.example }}"
exit 1
fi
echo "✅ Required files found in ${{ matrix.example }}"
- name: Build example
run: |
cd examples/${{ matrix.example }}
# Use GOWORK=off to treat each example as independent
echo "🔨 Building ${{ matrix.example }}..."
GOWORK=off go mod download
GOWORK=off go mod verify
GOWORK=off go build -v .
echo "✅ ${{ matrix.example }} builds successfully"
- name: Test example startup
run: |
cd examples/${{ matrix.example }}
echo "🚀 Testing ${{ matrix.example }} startup..."
# Build the example first
GOWORK=off go build -o example .
# Start the example in background and test it can start
if [ "${{ matrix.example }}" = "basic-app" ]; then
# Basic app just needs to start and respond to health check
timeout 10s ./example &
PID=$!
sleep 3
# Test health endpoint
if curl -f http://localhost:8080/health; then
echo "✅ basic-app health check passed"
else
echo "❌ basic-app health check failed"
kill $PID 2>/dev/null || true
exit 1
fi
kill $PID 2>/dev/null || true
elif [ "${{ matrix.example }}" = "multi-tenant-app" ]; then
# Multi-tenant app needs special validation to ensure tenants are loaded
echo "🏢 Testing multi-tenant app with tenant validation..."
# Run the app and capture logs
timeout 10s ./example > app.log 2>&1 &
PID=$!
sleep 5
# Check if process is still running
if ! kill -0 $PID 2>/dev/null; then
echo "❌ multi-tenant-app crashed during startup"
cat app.log
exit 1
fi
# Validate that tenants were loaded successfully
if grep -q "Successfully loaded tenant configurations" app.log; then
echo "✅ multi-tenant-app successfully loaded tenant configurations"
else
echo "❌ multi-tenant-app failed to load tenant configurations"
echo "📋 Application logs:"
cat app.log
kill $PID 2>/dev/null || true
exit 1
fi
# Check for any tenant loading errors
if grep -q "Failed to load tenant config" app.log; then
echo "❌ multi-tenant-app encountered tenant loading errors"
echo "📋 Application logs:"
cat app.log
kill $PID 2>/dev/null || true
exit 1
fi
# Validate that expected tenants were registered
if grep -q "tenantCount=2" app.log; then
echo "✅ multi-tenant-app loaded expected number of tenants"
else
echo "❌ multi-tenant-app did not load expected number of tenants"
echo "📋 Application logs:"
cat app.log
kill $PID 2>/dev/null || true
exit 1
fi
kill $PID 2>/dev/null || true
elif [ "${{ matrix.example }}" = "testing-scenarios" ]; then
# Testing scenarios example has comprehensive validation scripts
echo "🧪 Testing testing-scenarios with validation scripts..."
# Make scripts executable
chmod +x *.sh
# Run the demo script (includes comprehensive testing)
echo "Running demo.sh for rapid validation..."
if timeout 60s ./demo.sh; then
echo "✅ testing-scenarios demo script passed"
else
echo "❌ testing-scenarios demo script failed"
exit 1
fi
# Run health check validation
echo "Running health check validation..."
if timeout 30s ./test-health-checks.sh; then
echo "✅ testing-scenarios health check validation passed"
else
echo "❌ testing-scenarios health check validation failed"
exit 1
fi
# Run feature flag testing
echo "Running feature flag validation..."
if timeout 30s ./test-feature-flags.sh; then
echo "✅ testing-scenarios feature flag validation passed"
else
echo "❌ testing-scenarios feature flag validation failed"
exit 1
fi
elif [ "${{ matrix.example }}" = "health-aware-reverse-proxy" ]; then
# Health-aware reverse proxy needs comprehensive circuit breaker testing
echo "🔄 Testing health-aware-reverse-proxy with circuit breaker validation..."
# Make test script executable
chmod +x test-circuit-breakers.sh
# Start the application in background
timeout 60s ./example > app.log 2>&1 &
PID=$!
sleep 8 # Allow time for mock backends to start
# Check if process is still running
if ! kill -0 $PID 2>/dev/null; then
echo "❌ health-aware-reverse-proxy crashed during startup"
cat app.log
exit 1
fi
# Test basic health endpoint (accepts both 200 and 503 status codes)
echo "Testing basic health endpoint..."
health_response=$(curl -s -w "HTTP_CODE:%{http_code}" http://localhost:8080/health)
http_code=$(echo "$health_response" | grep -o "HTTP_CODE:[0-9]*" | cut -d: -f2)
if [ "$http_code" = "200" ] || [ "$http_code" = "503" ]; then
echo "✅ health-aware-reverse-proxy health endpoint responding (HTTP $http_code)"
else
echo "❌ health-aware-reverse-proxy health endpoint returned unexpected status: HTTP $http_code"
echo "Response: $health_response"
kill $PID 2>/dev/null || true
exit 1
fi
# Test that unreachable backend triggers circuit breaker (simplified test)
echo "Testing circuit breaker functionality..."
# Make 3 requests to unreachable API to trigger circuit breaker
for i in {1..3}; do
curl -s http://localhost:8080/api/unreachable > /dev/null || true
done
# Wait a moment for circuit breaker to update
sleep 2
# Check that health status reflects circuit breaker state
health_response=$(curl -s http://localhost:8080/health)
if echo "$health_response" | grep -q '"circuit_open_count":[1-9]'; then
echo "✅ health-aware-reverse-proxy circuit breaker properly triggered"
else
echo "⚠️ Circuit breaker may not have triggered as expected (this could be timing-related)"
echo "Health response: $health_response"
# Don't fail here as this could be timing-sensitive in CI
fi
kill $PID 2>/dev/null || true
elif [ "${{ matrix.example }}" = "observer-pattern" ]; then
# Observer pattern example needs to complete its demo and show success message
echo "🔍 Testing observer-pattern example completion..."
# Run the observer pattern demo and capture output
timeout 30s ./example > app.log 2>&1
EXIT_CODE=$?
# Check if the demo completed successfully
if [ $EXIT_CODE -eq 0 ] && grep -q "Observer Pattern Demo completed successfully" app.log; then
echo "✅ observer-pattern demo completed successfully"
# Verify key lifecycle signals were logged (updated patterns)
# We now log structured messages like "Registered service" and "Initialized module".
if grep -q "Registered service" app.log && grep -q "Initialized module" app.log; then
echo "✅ observer-pattern logged expected lifecycle events"
else
echo "❌ observer-pattern missing expected lifecycle events (looking for 'Registered service' and 'Initialized module')"
echo "📋 Application logs:"
cat app.log
exit 1
fi
# Verify CloudEvents functionality was tested
if grep -q "CloudEvent emitted successfully" app.log; then
echo "✅ observer-pattern CloudEvents functionality verified"
else
echo "❌ observer-pattern CloudEvents functionality not verified"
echo "📋 Application logs:"
cat app.log
exit 1
fi
else
echo "❌ observer-pattern demo failed to complete successfully"
echo "📋 Application logs:"
cat app.log
exit 1
fi
elif [ "${{ matrix.example }}" = "multi-engine-eventbus" ]; then
# Multi-engine eventbus example - use Redis for external service demo
echo "🔄 Testing multi-engine-eventbus with Redis service..."
# Make run-demo.sh executable
chmod +x run-demo.sh
# Check if Docker is available for Redis
if command -v docker &> /dev/null; then
echo "Docker available, testing with Redis service"
# Try to start Redis and run the demo
if timeout 60s ./run-demo.sh run-redis; then
echo "✅ multi-engine-eventbus demo completed successfully with Redis"
# Clean up Redis container
docker-compose -f docker-compose.yml down -v 2>/dev/null || true
else
echo "⚠️ Redis demo failed, testing graceful degradation mode"
# Clean up any containers
docker-compose -f docker-compose.yml down -v 2>/dev/null || true
# Test graceful degradation (this should always work)
timeout 10s ./example &
PID=$!
sleep 5
if kill -0 $PID 2>/dev/null; then
echo "✅ multi-engine-eventbus handles missing services gracefully"
kill $PID 2>/dev/null || true
else
echo "❌ multi-engine-eventbus failed to handle missing services"
exit 1
fi
fi
else
echo "Docker not available, testing graceful degradation mode only"
# Test without external services (graceful degradation)
timeout 10s ./example &
PID=$!
sleep 5
if kill -0 $PID 2>/dev/null; then
echo "✅ multi-engine-eventbus handles missing services gracefully"
kill $PID 2>/dev/null || true
else
echo "❌ multi-engine-eventbus failed to handle missing services"
exit 1
fi
fi
elif [ "${{ matrix.example }}" = "reverse-proxy" ] || [ "${{ matrix.example }}" = "http-client" ] || [ "${{ matrix.example }}" = "advanced-logging" ] || [ "${{ matrix.example }}" = "verbose-debug" ] || [ "${{ matrix.example }}" = "instance-aware-db" ] || [ "${{ matrix.example }}" = "feature-flag-proxy" ]; then
# These apps just need to start without immediate errors
timeout 5s ./example &
PID=$!
sleep 3
# Check if process is still running (no immediate crash)
if kill -0 $PID 2>/dev/null; then
echo "✅ ${{ matrix.example }} started successfully"
kill $PID 2>/dev/null || true
else
echo "❌ ${{ matrix.example }} failed to start or crashed immediately"
exit 1
fi
fi
echo "✅ ${{ matrix.example }} startup test passed"
- name: Verify go.mod configuration
run: |
cd examples/${{ matrix.example }}
echo "🔍 Verifying go.mod configuration for ${{ matrix.example }}..."
# Allow either a short module name (directory) or fully-qualified path under the repo
MODULE_NAME=$(grep "^module " go.mod | awk '{print $2}')
SHORT_NAME="${{ matrix.example }}"
FQ_EXPECTED="github.com/GoCodeAlone/modular/examples/${{ matrix.example }}"
if [ "$MODULE_NAME" != "$SHORT_NAME" ] && [ "$MODULE_NAME" != "$FQ_EXPECTED" ]; then
echo "❌ Module name unexpected in ${{ matrix.example }}"
echo "Found: $MODULE_NAME"
echo "Expected one of: $SHORT_NAME OR $FQ_EXPECTED"
exit 1
fi
# The replace directive is optional when using go.work; warn if absent but don't fail.
if ! grep -q "replace .*github.com/GoCodeAlone/modular => ../../" go.mod; then
echo "⚠️ Warning: replace directive to root module not found (acceptable when using go.work)."
fi
echo "✅ go.mod configuration verified for ${{ matrix.example }} (module: $MODULE_NAME)"
examples-overview:
name: Examples Overview
runs-on: ubuntu-latest
needs: validate-examples
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Generate examples summary
run: |
echo "# 📋 Examples Validation Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "All examples have been validated successfully!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "## 🎯 Validated Examples" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
cd examples
for example in */; do
example=${example%/}
echo "- **$example**: ✅ Build and startup tests passed" >> $GITHUB_STEP_SUMMARY
done
echo "" >> $GITHUB_STEP_SUMMARY
echo "## 🧪 Test Coverage" >> $GITHUB_STEP_SUMMARY
echo "- Structure validation: ✅" >> $GITHUB_STEP_SUMMARY
echo "- Build verification: ✅" >> $GITHUB_STEP_SUMMARY
echo "- Startup testing: ✅" >> $GITHUB_STEP_SUMMARY
echo "- Configuration validation: ✅" >> $GITHUB_STEP_SUMMARY