#!/bin/bash # Landing Page Cookbook Test Script # Tests the full landing page flow from cookbooks/landing-page.md # # Usage: # ./cookbooks/scripts/landing-test.sh run # Run the full flow # ./cookbooks/scripts/landing-test.sh teardown # Clean up test resources # ./cookbooks/scripts/landing-test.sh status # 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}" PROJECT_NAME="${1:-landing-test}" TEMPLATE="astro-landing" # 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"; } # Configuration for polling PIPELINE_TIMEOUT=300 # 5 minutes max wait for pipeline PIPELINE_POLL_INTERVAL=10 # Check every 10 seconds SITE_TIMEOUT=60 # 1 minute 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 # Returns: 0 on success, 1 on failure/timeout 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 (API returns array at .data, not .data.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) log_success "Pipeline #$pipeline_number completed successfully (${elapsed}s)" return 0 ;; failure|error|killed|declined) 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 # Returns: 0 on success, 1 on failure/timeout 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 } # Test adding a DNS alias test_dns_alias() { local project_name="$1" local alias_domain="$2" log_info "Testing DNS alias: $alias_domain" local response response=$(api_call POST "/projects/$project_name/domains" "{\"domain\": \"$alias_domain\"}") if echo "$response" | jq -e '.error' > /dev/null 2>&1; then local error_code error_code=$(echo "$response" | jq -r '.error.code // "UNKNOWN"') if [[ "$error_code" == "DOMAIN_EXISTS" ]]; then log_warn "Domain alias already exists: $alias_domain" return 0 fi log_error "Failed to add DNS alias" echo "$response" | jq . return 1 fi log_success "DNS alias added: $alias_domain" echo "$response" | jq '.data | {domain, type, dns_record_id}' return 0 } # Remove a DNS alias remove_dns_alias() { local project_name="$1" local alias_domain="$2" log_info "Removing DNS alias: $alias_domain" local response response=$(api_call DELETE "/projects/$project_name/domains/$alias_domain") if echo "$response" | jq -e '.error' > /dev/null 2>&1; then local error_code error_code=$(echo "$response" | jq -r '.error.code // "UNKNOWN"') if [[ "$error_code" == "NOT_FOUND" ]]; then log_warn "Domain alias not found (already deleted?): $alias_domain" return 0 fi log_warn "Failed to remove DNS alias: $alias_domain" return 1 fi log_success "DNS alias removed: $alias_domain" return 0 } run_flow() { local project_name="${1:-landing-test}" local custom_subdomain="${2:-}" echo "" echo "==========================================" echo " Landing Page Cookbook Test" echo " Project: $project_name" if [[ -n "$custom_subdomain" ]]; then echo " Custom subdomain: $custom_subdomain" fi echo "==========================================" echo "" # Step 0: Health check check_health || exit 1 echo "" # Step 1: Create project log_info "Step 1: Creating project with $TEMPLATE template..." local create_payload="{ \"name\": \"$project_name\", \"description\": \"Cookbook test: landing page flow\", \"template\": \"$TEMPLATE\"" if [[ -n "$custom_subdomain" ]]; then create_payload="$create_payload, \"custom_subdomain\": \"$custom_subdomain\"" fi create_payload="$create_payload }" local create_response create_response=$(api_call POST "/project" "$create_payload") if echo "$create_response" | jq -e '.error' > /dev/null 2>&1; then log_error "Failed to create project" echo "$create_response" | jq . exit 1 fi log_success "Project created" echo "$create_response" | jq '{ project_id: .data.project_id, slug: .data.slug, git_url: .data.git.html_url, domain: .data.domain, url: .data.url, domains: .data.domains, next_steps: .data.next_steps }' # Extract domain info local primary_domain local slug primary_domain=$(echo "$create_response" | jq -r '.data.domain') slug=$(echo "$create_response" | jq -r '.data.slug // empty') if [[ -n "$slug" ]]; then log_success "Auto-generated slug: $slug" fi # Check for manual steps local next_steps next_steps=$(echo "$create_response" | jq -r '.data.next_steps[]?' 2>/dev/null || echo "") if [[ -n "$next_steps" ]]; then echo "" log_warn "Some steps require manual intervention:" echo "$create_response" | jq -r '.data.next_steps[]' echo "" log_info "Check API logs for details: ./scripts/logs.sh -e" fi echo "" # Step 2: List all domains log_info "Step 2: Listing all project domains..." local domains_response domains_response=$(api_call GET "/projects/$project_name/domains") if echo "$domains_response" | jq -e '.error' > /dev/null 2>&1; then log_warn "Could not list domains" echo "$domains_response" | jq . else local domain_count domain_count=$(echo "$domains_response" | jq -r '.data.total') log_success "Found $domain_count domain(s)" echo "$domains_response" | jq '.data.domains[] | {domain, type, verified}' fi echo "" # Step 3: Verify project status log_info "Step 3: Verifying project status..." sleep 2 # Give DNS/Gitea a moment local status_response status_response=$(api_call GET "/project/$project_name") if echo "$status_response" | jq -e '.error' > /dev/null 2>&1; then log_warn "Could not fetch project status" echo "$status_response" | jq . else log_success "Project status retrieved" echo "$status_response" | jq '{ name: .data.name, slug: .data.slug, domain: .data.domain, url: .data.url, domains: .data.domains, git: .data.git.html_url, deployment: .data.deployment }' fi echo "" # Step 4: Check DNS log_info "Step 4: Checking DNS resolution..." if host "$primary_domain" > /dev/null 2>&1; then log_success "DNS resolves: $primary_domain" host "$primary_domain" | head -1 else log_warn "DNS not yet resolving: $primary_domain (may take a few minutes)" fi echo "" # Step 5: Wait for CI pipeline log_info "Step 5: Monitoring CI pipeline..." local pipeline_success=false if wait_for_pipeline "$project_name"; then pipeline_success=true else log_warn "Pipeline did not complete successfully - site may not deploy" log_info "Check Woodpecker: https://ci.threesix.ai/threesix/$project_name" fi echo "" # Step 6: Verify site is live (only if pipeline succeeded) local site_live=false if [[ "$pipeline_success" == "true" ]]; then log_info "Step 6: Verifying site is accessible..." if wait_for_site "$primary_domain"; then site_live=true # Show a snippet of the response log_info "Fetching site content preview..." curl -s "https://$primary_domain" | head -20 | grep -E '|<h1' || true fi else log_info "Step 6: Skipping site verification (pipeline not successful)" fi echo "" # Step 7: Test DNS alias functionality local test_alias="${project_name}-alias.threesix.ai" log_info "Step 7: Testing DNS alias functionality..." if test_dns_alias "$project_name" "$test_alias"; then log_success "DNS alias test passed" # Clean up test alias immediately sleep 2 remove_dns_alias "$project_name" "$test_alias" else log_warn "DNS alias test failed - check Cloudflare permissions" fi echo "" # Step 8: Final domain listing log_info "Step 8: Final domain listing..." domains_response=$(api_call GET "/projects/$project_name/domains") if echo "$domains_response" | jq -e '.data.domains' > /dev/null 2>&1; then echo "$domains_response" | jq '.data.domains[] | {domain, type, is_primary}' fi echo "" # Summary echo "==========================================" echo " Test Results Summary" echo "==========================================" echo "" echo " Project: $project_name" echo " Git repo: $(echo "$create_response" | jq -r '.data.git.html_url')" if [[ -n "$slug" ]]; then echo " Slug: $slug" fi echo " Primary: https://$primary_domain" echo "" echo " Test Results:" echo -e " Project created: ${GREEN}PASS${NC}" if [[ "$pipeline_success" == "true" ]]; then echo -e " CI Pipeline: ${GREEN}PASS${NC}" else echo -e " CI Pipeline: ${RED}FAIL${NC}" fi if [[ "$site_live" == "true" ]]; then echo -e " Site accessible: ${GREEN}PASS${NC}" else echo -e " Site accessible: ${YELLOW}PENDING${NC}" fi echo -e " DNS alias: ${GREEN}TESTED${NC}" echo "" echo " All domains:" echo "$domains_response" | jq -r '.data.domains[]? | " - \(.domain) (\(.type))"' 2>/dev/null || echo " (none listed)" echo "" echo " Useful commands:" echo " Check status: ./cookbooks/scripts/landing-test.sh status $project_name" echo " View logs: ./scripts/logs.sh -e" echo " Woodpecker: https://ci.threesix.ai/threesix/$project_name" echo " Teardown: ./cookbooks/scripts/landing-test.sh teardown $project_name" echo "" # Return appropriate exit code if [[ "$pipeline_success" == "true" && "$site_live" == "true" ]]; then log_success "Full E2E test PASSED" return 0 elif [[ "$pipeline_success" == "true" ]]; then log_warn "Partial success - pipeline passed but site not yet live" return 0 else log_error "E2E test FAILED - pipeline did not complete" return 1 fi } teardown() { local project_name="${1:-landing-test}" echo "" echo "==========================================" echo " Teardown: $project_name" echo "==========================================" echo "" # First, list domains that will be deleted log_info "Checking domains to be deleted..." local domains_response domains_response=$(api_call GET "/projects/$project_name/domains") if echo "$domains_response" | jq -e '.data.total' > /dev/null 2>&1; then local domain_count domain_count=$(echo "$domains_response" | jq -r '.data.total') log_info "Will delete $domain_count domain(s):" echo "$domains_response" | jq -r '.data.domains[]? | " - \(.domain)"' echo "" fi log_info "Deleting project $project_name..." local response response=$(api_call DELETE "/project/$project_name") if echo "$response" | jq -e '.data.status == "deleted"' > /dev/null 2>&1; then log_success "Project deleted" echo "$response" | jq . elif echo "$response" | jq -e '.error.code == "NOT_FOUND"' > /dev/null 2>&1; then log_warn "Project not found (already deleted?)" else log_error "Failed to delete project" echo "$response" | jq . exit 1 fi echo "" log_info "Note: Gitea repo is preserved for safety. Delete manually if needed:" echo " https://git.threesix.ai/jordan/$project_name/settings" echo "" } status() { local project_name="${1:-landing-test}" echo "" log_info "Fetching status for: $project_name" echo "" local response response=$(api_call GET "/project/$project_name") if echo "$response" | jq -e '.error' > /dev/null 2>&1; then log_error "Project not found or error" echo "$response" | jq . exit 1 fi echo "$response" | jq '{ name: .data.name, description: .data.description, slug: .data.slug, domain: .data.domain, url: .data.url, domains: .data.domains, git: .data.git, deployment: .data.deployment }' echo "" log_info "Listing all domains..." local domains_response domains_response=$(api_call GET "/projects/$project_name/domains") if echo "$domains_response" | jq -e '.data.domains' > /dev/null 2>&1; then echo "$domains_response" | jq '.data.domains' fi } # Main case "${1:-}" in run) shift run_flow "${1:-landing-test}" "${2:-}" ;; teardown) shift teardown "${1:-landing-test}" ;; status) shift status "${1:-landing-test}" ;; *) echo "Usage: $0 {run|teardown|status} [project-name] [custom-subdomain]" echo "" echo "Commands:" echo " run [name] [subdomain] Create project and run full flow" echo " teardown [name] Delete project and clean up" echo " status [name] Check current project status" echo "" echo "Full E2E flow tested:" echo " 1. Project creation with template (POST /project)" echo " 2. Gitea repo + DNS + Woodpecker CI activation" echo " 3. Template seeding (astro-landing)" echo " 4. CI pipeline monitoring (GET /projects/{id}/pipelines)" echo " 5. Site deployment verification (HTTP 200 check)" echo " 6. DNS alias add/remove (POST/DELETE /projects/{id}/domains)" echo " 7. Multi-domain listing" echo "" echo "Timeouts:" echo " Pipeline: ${PIPELINE_TIMEOUT:-300}s, Site: ${SITE_TIMEOUT:-60}s" echo "" echo "Environment:" echo " RDEV_API_URL API endpoint (default: https://rdev.masq-ops.orchard9.ai)" echo " RDEV_API_KEY API key (required)" echo "" echo "Examples:" echo " $0 run # Test with auto-slug only" echo " $0 run my-landing # Test with custom project name" echo " $0 run my-landing my-site # Also create my-site.threesix.ai" echo " $0 status my-landing # Check status and domains" echo " $0 teardown my-landing # Clean up (deletes all domains)" exit 1 ;; esac