rdev/cookbooks/scripts/landing-test.sh
jordan 8282d60c69 feat: implement composable monorepo template system with component architecture
Adds the composable monorepo template system that generates project skeletons
with pluggable components (service, worker, app-react, app-astro, cli).

Key changes:
- Monorepo skeleton templates with shared pkg/, scripts/, and git hooks
- Component templates (service, worker, app-react, app-astro, cli) with
  Dockerfiles, CI steps, and component.yaml manifests
- Component domain model with validation and dependency resolution
- Component handler endpoints for CRUD and composition
- Template provider extended with BuildComposableProject and component assembly
- Deployer extended with composable project deployment support
- Handler timeout constants (TimeoutFastLookup through TimeoutLongRunning)
- envutil package for centralized env var reads with defaults
- api.DecodeJSON helper for standardized request body decoding
- Standardized response helpers (WriteBadRequest, WriteNotFound, etc.)
- Replaced fullstack-app cookbook with composable-app cookbook
- Hardened handler timeouts, logging, and error responses across all handlers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:11:42 -07:00

325 lines
9.5 KiB
Bash
Executable File

#!/bin/bash
# Landing Page Cookbook Test Script
# Tests the composable landing page flow from cookbooks/landing-page.md
#
# Flow:
# 1. Create project (monorepo skeleton)
# 2. Add app-astro component
# 3. Wait for CI pipeline
# 4. Verify site is live
#
# Usage:
# ./cookbooks/scripts/landing-test.sh run [name] # Run the full flow
# ./cookbooks/scripts/landing-test.sh teardown [name] # Clean up test resources
# ./cookbooks/scripts/landing-test.sh status [name] # Check current status
set -euo pipefail
# Configuration
API_URL="${RDEV_API_URL:-https://rdev.masq-ops.orchard9.ai}"
API_KEY="${RDEV_API_KEY:?RDEV_API_KEY environment variable required}"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Timeouts
PIPELINE_TIMEOUT=300 # 5 minutes max wait for CI pipeline
PIPELINE_POLL_INTERVAL=10
SITE_TIMEOUT=120 # 2 minutes max wait for site to be live
api_call() {
local method="$1"
local endpoint="$2"
local data="${3:-}"
if [[ -n "$data" ]]; then
curl -s -X "$method" "${API_URL}${endpoint}" \
-H "X-API-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d "$data"
else
curl -s -X "$method" "${API_URL}${endpoint}" \
-H "X-API-Key: ${API_KEY}"
fi
}
check_health() {
log_info "Checking API health..."
local response
response=$(curl -s "${API_URL}/health")
if echo "$response" | jq -e '.data.status == "ok"' > /dev/null 2>&1; then
log_success "API is healthy"
return 0
else
log_error "API health check failed"
echo "$response" | jq .
return 1
fi
}
# Wait for pipeline to appear and complete
wait_for_pipeline() {
local project_name="$1"
local start_time=$(date +%s)
local pipeline_found=false
local pipeline_number=""
local pipeline_status=""
log_info "Waiting for CI pipeline to start (timeout: ${PIPELINE_TIMEOUT}s)..."
while true; do
local elapsed=$(($(date +%s) - start_time))
if [[ $elapsed -ge $PIPELINE_TIMEOUT ]]; then
log_error "Pipeline timeout after ${PIPELINE_TIMEOUT}s"
return 1
fi
local response
response=$(api_call GET "/projects/$project_name/pipelines" 2>/dev/null || echo "{}")
# Check if we have pipelines
local pipeline_count
pipeline_count=$(echo "$response" | jq -r '.data | length' 2>/dev/null || echo "0")
if [[ "$pipeline_count" -gt 0 ]]; then
if [[ "$pipeline_found" == "false" ]]; then
pipeline_found=true
pipeline_number=$(echo "$response" | jq -r '.data[0].number')
log_success "Pipeline #$pipeline_number started"
fi
# Get latest pipeline status
pipeline_status=$(echo "$response" | jq -r '.data[0].status')
case "$pipeline_status" in
success)
echo ""
log_success "Pipeline #$pipeline_number completed successfully (${elapsed}s)"
return 0
;;
failure|error|killed|declined)
echo ""
log_error "Pipeline #$pipeline_number failed with status: $pipeline_status"
echo "$response" | jq '.data[0]'
return 1
;;
running|pending)
echo -ne "\r${BLUE}[INFO]${NC} Pipeline #$pipeline_number status: $pipeline_status (${elapsed}s)... "
;;
*)
echo -ne "\r${BLUE}[INFO]${NC} Pipeline #$pipeline_number status: $pipeline_status (${elapsed}s)... "
;;
esac
else
echo -ne "\r${BLUE}[INFO]${NC} Waiting for pipeline to start (${elapsed}s)... "
fi
sleep $PIPELINE_POLL_INTERVAL
done
}
# Wait for site to be accessible
wait_for_site() {
local domain="$1"
local start_time=$(date +%s)
log_info "Waiting for site to be live: https://$domain (timeout: ${SITE_TIMEOUT}s)..."
while true; do
local elapsed=$(($(date +%s) - start_time))
if [[ $elapsed -ge $SITE_TIMEOUT ]]; then
log_warn "Site timeout after ${SITE_TIMEOUT}s - may still be deploying"
return 1
fi
local http_code
http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "https://$domain" 2>/dev/null || echo "000")
if [[ "$http_code" == "200" ]]; then
echo ""
log_success "Site is live! HTTP $http_code (${elapsed}s)"
return 0
elif [[ "$http_code" == "000" ]]; then
echo -ne "\r${BLUE}[INFO]${NC} Waiting for site... (${elapsed}s, connection failed) "
else
echo -ne "\r${BLUE}[INFO]${NC} Waiting for site... (${elapsed}s, HTTP $http_code) "
fi
sleep 3
done
}
# Main run flow
run_flow() {
local project_name="$1"
echo ""
echo "=========================================="
echo " Landing Page E2E Test (Composable)"
echo "=========================================="
echo ""
echo "Project: $project_name"
echo ""
# Step 0: Health check
check_health || exit 1
echo ""
# Step 1: Create project (monorepo skeleton)
log_info "Step 1: Creating project skeleton..."
local create_response
create_response=$(api_call POST "/projects" "{\"name\": \"$project_name\", \"description\": \"Landing page E2E test\"}")
local domain
domain=$(echo "$create_response" | jq -r '.data.domain // ""')
if [[ -z "$domain" || "$domain" == "null" ]]; then
log_error "Failed to create project"
echo "$create_response" | jq .
exit 1
fi
log_success "Project created: $project_name"
echo " Domain: $domain"
echo " Git: https://git.threesix.ai/jordan/$project_name"
echo ""
# Step 2: Add app-astro component
log_info "Step 2: Adding landing page component (app-astro)..."
local component_response
component_response=$(api_call POST "/projects/$project_name/components" '{"type": "app", "name": "landing", "template": "app-astro"}')
local component_path
component_path=$(echo "$component_response" | jq -r '.data.path // ""')
if [[ -z "$component_path" || "$component_path" == "null" ]]; then
log_error "Failed to add component"
echo "$component_response" | jq .
exit 1
fi
local component_port
component_port=$(echo "$component_response" | jq -r '.data.port // "N/A"')
log_success "Component added: $component_path (port: $component_port)"
echo ""
# Step 3: Wait for pipeline
log_info "Step 3: Waiting for CI pipeline..."
echo ""
if ! wait_for_pipeline "$project_name"; then
log_warn "Pipeline failed, but continuing to check if site is accessible..."
fi
echo ""
# Step 4: Wait for site
log_info "Step 4: Verifying site is accessible..."
if wait_for_site "$domain"; then
log_success "Site verified!"
else
log_warn "Site not accessible yet, may need more time"
fi
echo ""
# Summary
echo "=========================================="
echo " Test Complete"
echo "=========================================="
echo ""
echo " Site URL: https://$domain"
echo " Git: https://git.threesix.ai/jordan/$project_name"
echo " CI: https://ci.threesix.ai/jordan/$project_name"
echo ""
echo " To customize: POST /projects/$project_name/builds with a prompt"
echo " To teardown: $0 teardown $project_name"
echo ""
}
# Check status
check_status() {
local project_name="$1"
echo ""
echo "=== Project Status: $project_name ==="
echo ""
log_info "Project info:"
api_call GET "/projects/$project_name" | jq '.data // .'
echo ""
log_info "Components:"
api_call GET "/projects/$project_name/components" | jq '.data // .'
echo ""
log_info "Latest pipelines:"
api_call GET "/projects/$project_name/pipelines" | jq '.data[:3] // .'
echo ""
}
# Teardown
teardown() {
local project_name="$1"
echo ""
log_info "Tearing down project: $project_name"
local response
response=$(api_call DELETE "/projects/$project_name")
if echo "$response" | jq -e '.error' > /dev/null 2>&1; then
log_error "Teardown failed"
echo "$response" | jq .
exit 1
fi
log_success "Project deleted (Gitea repo preserved)"
echo "$response" | jq '.data // .'
echo ""
}
# Parse command
COMMAND="${1:-}"
PROJECT_NAME="${2:-landing-test-$(date +%s)}"
case "$COMMAND" in
run)
run_flow "$PROJECT_NAME"
;;
status)
check_status "$PROJECT_NAME"
;;
teardown)
teardown "$PROJECT_NAME"
;;
*)
echo "Landing Page E2E Test Script"
echo ""
echo "Usage: $0 <command> [project-name]"
echo ""
echo "Commands:"
echo " run Run the full composable landing page flow"
echo " status Check project and component status"
echo " teardown Delete project (preserves git repo)"
echo ""
echo "Examples:"
echo " $0 run my-landing"
echo " $0 status my-landing"
echo " $0 teardown my-landing"
echo ""
exit 1
;;
esac