diff --git a/cookbooks/scripts/landing-test.sh b/cookbooks/scripts/landing-test.sh index 7ea8253..d4cda13 100755 --- a/cookbooks/scripts/landing-test.sh +++ b/cookbooks/scripts/landing-test.sh @@ -27,6 +27,11 @@ 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" @@ -57,6 +62,150 @@ check_health() { 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:-}" @@ -180,34 +329,100 @@ run_flow() { fi echo "" - # Step 5: Summary + # 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 " Summary" + 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 " Next steps:" - echo " 1. If CI/template failed, check logs: ./scripts/logs.sh -e" - echo " 2. Push code to trigger CI build" - echo " 3. Monitor pipeline: curl -s -H 'X-API-Key: \$RDEV_API_KEY' $API_URL/projects/$project_name/pipelines | jq" - echo " 4. Verify site: curl -I https://$primary_domain" - echo "" - echo " Add custom domain:" - echo " curl -X POST -H 'X-API-Key: \$RDEV_API_KEY' -H 'Content-Type: application/json' \\" - echo " -d '{\"domain\":\"my-site.threesix.ai\"}' \\" - echo " $API_URL/projects/$project_name/domains" - echo "" - echo " Teardown:" - echo " ./cookbooks/scripts/landing-test.sh teardown $project_name" + 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() { @@ -312,11 +527,17 @@ case "${1:-}" in echo " teardown [name] Delete project and clean up" echo " status [name] Check current project status" echo "" - echo "Features tested:" - echo " - Auto-generated random slug (8-char) for primary domain" - echo " - Optional custom subdomain (e.g., 'my-app' -> my-app.threesix.ai)" - echo " - Multi-domain listing via /projects/{id}/domains" - echo " - DNS record creation and cleanup" + 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)"