#!/bin/bash set -euo pipefail # Feature Development E2E Test Script # Tests the complete feature development workflow: # 1. Create composable project with skeleton # 2. Add service component # 3. Add app-nextjs component # 4. Verify chassis patterns are available # 5. Verify design system packages exist # 6. Test auth integration # 7. Verify CI pipeline # # Usage: ./cookbooks/scripts/feature-test.sh # Commands: run, status, verify-patterns, teardown SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/common.sh" # Parse --auto-teardown flag from args ARGS=("$@") for i in "${!ARGS[@]}"; do if [[ "${ARGS[$i]}" == "--auto-teardown" ]]; then AUTO_TEARDOWN="true" unset 'ARGS[$i]' fi done ARGS=("${ARGS[@]}") # Re-index array COMMAND="${ARGS[0]:-}" PROJECT_NAME="${ARGS[1]:-}" # Register cleanup trap for auto-teardown register_cleanup_trap if [[ -z "$COMMAND" || -z "$PROJECT_NAME" ]]; then echo "Usage: $0 " echo "Commands:" echo " run - Create project with full stack and verify patterns" echo " status - Check project status" echo " verify-patterns - Verify chassis and design system patterns exist" echo " teardown - Delete the project" exit 1 fi # Verify chassis patterns in the created project verify_chassis_patterns() { local project_id="$1" local git_owner git_owner=$(get_git_owner) print_header "Verifying Chassis Patterns" # Check pkg/httperror exists echo "Checking pkg/httperror..." local httperror_check httperror_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/pkg/httperror/error.go" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$httperror_check" == "error.go" ]]; then print_success "pkg/httperror/error.go exists" else print_warning "pkg/httperror/error.go not found (may be named differently)" fi # Check pkg/app/handler.go (Wrap pattern) echo "Checking pkg/app/handler.go (Wrap pattern)..." local handler_check handler_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/pkg/app/handler.go" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$handler_check" == "handler.go" ]]; then print_success "pkg/app/handler.go exists (Wrap pattern)" else print_warning "pkg/app/handler.go not found" fi # Check pkg/app/bind.go echo "Checking pkg/app/bind.go..." local bind_check bind_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/pkg/app/bind.go" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$bind_check" == "bind.go" ]]; then print_success "pkg/app/bind.go exists (Bind pattern)" else print_warning "pkg/app/bind.go not found" fi # Check pkg/app/health.go echo "Checking pkg/app/health.go..." local health_check health_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/pkg/app/health.go" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$health_check" == "health.go" ]]; then print_success "pkg/app/health.go exists (Health probes)" else print_warning "pkg/app/health.go not found" fi # Check pkg/auth exists echo "Checking pkg/auth..." local auth_check auth_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/pkg/auth" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r 'if type == "array" then "directory" else "not found" end') if [[ "$auth_check" == "directory" ]]; then print_success "pkg/auth/ directory exists" else print_warning "pkg/auth/ not found" fi # Check pkg/chassis exists echo "Checking pkg/chassis..." local chassis_check chassis_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/pkg/chassis/chassis.go" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$chassis_check" == "chassis.go" ]]; then print_success "pkg/chassis/chassis.go exists (facade)" else print_warning "pkg/chassis/chassis.go not found" fi # Check pkg/openapi exists echo "Checking pkg/openapi..." local openapi_check openapi_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/pkg/openapi" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r 'if type == "array" then "directory" else "not found" end') if [[ "$openapi_check" == "directory" ]]; then print_success "pkg/openapi/ directory exists (spec builder + docs)" else print_warning "pkg/openapi/ not found" fi } # Verify design system packages verify_design_system() { local project_id="$1" local git_owner git_owner=$(get_git_owner) print_header "Verifying Design System Packages" # Check packages/ui exists echo "Checking packages/ui..." local ui_check ui_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/packages/ui/package.json" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$ui_check" == "package.json" ]]; then print_success "packages/ui exists" else print_warning "packages/ui not found" fi # Check packages/layout exists echo "Checking packages/layout..." local layout_check layout_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/packages/layout/package.json" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$layout_check" == "package.json" ]]; then print_success "packages/layout exists" else print_warning "packages/layout not found" fi # Check packages/auth exists echo "Checking packages/auth..." local auth_check auth_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/packages/auth/package.json" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$auth_check" == "package.json" ]]; then print_success "packages/auth exists" else print_warning "packages/auth not found" fi # Check packages/api-client exists echo "Checking packages/api-client..." local client_check client_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/packages/api-client/package.json" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$client_check" == "package.json" ]]; then print_success "packages/api-client exists" else print_warning "packages/api-client not found" fi } # Verify service component uses chassis patterns verify_service_patterns() { local project_id="$1" local git_owner git_owner=$(get_git_owner) print_header "Verifying Service Component" # Check services/api exists echo "Checking services/api..." local svc_check svc_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/services/api/cmd/server/main.go" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$svc_check" == "main.go" ]]; then print_success "services/api component exists" else print_warning "services/api not found" fi # Check services/api/internal/api/routes.go exists echo "Checking routes.go..." local routes_check routes_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/services/api/internal/api/routes.go" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$routes_check" == "routes.go" ]]; then print_success "services/api/internal/api/routes.go exists" else print_warning "routes.go not found" fi # Check services/api/internal/api/spec.go exists (OpenAPI) echo "Checking spec.go (OpenAPI)..." local spec_check spec_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/services/api/internal/api/spec.go" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$spec_check" == "spec.go" ]]; then print_success "services/api/internal/api/spec.go exists (OpenAPI spec)" else print_warning "spec.go not found" fi # Check services/api/internal/api/handlers/example_test.go exists echo "Checking example_test.go..." local test_check test_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/services/api/internal/api/handlers/example_test.go" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$test_check" == "example_test.go" ]]; then print_success "services/api/internal/api/handlers/example_test.go exists" else print_warning "example_test.go not found" fi } # Verify app-nextjs component verify_nextjs_app() { local project_id="$1" local git_owner git_owner=$(get_git_owner) print_header "Verifying Next.js App Component" # Check apps/dashboard exists echo "Checking apps/dashboard..." local app_check app_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/apps/dashboard/package.json" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$app_check" == "package.json" ]]; then print_success "apps/dashboard exists" else print_warning "apps/dashboard not found" fi # Check App Router structure echo "Checking App Router structure..." local layout_check layout_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/apps/dashboard/src/app/layout.tsx" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$layout_check" == "layout.tsx" ]]; then print_success "App Router layout.tsx exists" else print_warning "App Router layout.tsx not found" fi # Check dashboard route group echo "Checking (dashboard) route group..." local dashboard_check dashboard_check=$(curl -s "https://git.threesix.ai/api/v1/repos/$git_owner/$project_id/contents/apps/dashboard/src/app/(dashboard)/layout.tsx" \ -H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | jq -r '.name // "not found"') if [[ "$dashboard_check" == "layout.tsx" ]]; then print_success "(dashboard) route group exists" else print_warning "(dashboard) route group not found" fi } # Add a component and verify add_component() { local comp_type="$1" local comp_name="$2" echo "Adding $comp_type component: $comp_name" local payload payload=$(jq -n \ --arg type "$comp_type" \ --arg name "$comp_name" \ '{type: $type, name: $name}') local result result=$(api_call POST "/projects/$PROJECT_NAME/components" "$payload") local path path=$(echo "$result" | jq -r '.data.path // .path // ""') if [[ -z "$path" ]]; then print_error "Failed to add component" echo "$result" | jq '.' return 1 fi local port port=$(echo "$result" | jq -r '.data.port // .port // "N/A"') print_success "Added $comp_type/$comp_name at $path (port: $port)" return 0 } run_flow() { print_header "Feature Development E2E Test" echo "Project: $PROJECT_NAME" # Step 1: Create project (skeleton) print_header "Step 1: Creating project skeleton" local create_payload create_payload=$(jq -n \ --arg name "$PROJECT_NAME" \ --arg desc "Feature development E2E test" \ '{name: $name, description: $desc}') local create_result create_result=$(api_call POST "/project" "$create_payload") echo "$create_result" | jq '.' local domain domain=$(echo "$create_result" | jq -r '.data.domain // .domain // ""') if [[ -z "$domain" ]]; then print_error "Failed to create project" exit 1 fi # Track project for auto-cleanup CLEANUP_PROJECT="$PROJECT_NAME" print_success "Project created with domain: $domain" # Step 2: Add backend service print_header "Step 2: Adding backend service" if ! add_component "service" "api"; then exit 1 fi # Step 3: Add Next.js dashboard print_header "Step 3: Adding Next.js dashboard" if ! add_component "app-nextjs" "dashboard"; then exit 1 fi # Step 4: List components print_header "Step 4: Verifying components" local components components=$(api_call GET "/projects/$PROJECT_NAME/components") echo "$components" | jq '.data // .' local comp_count comp_count=$(echo "$components" | jq '.data.components | length // 0') if [[ "$comp_count" -lt 2 ]]; then print_warning "Expected 2 components, got $comp_count" else print_success "All components added successfully" fi # Step 5: Wait for initial commit to propagate print_header "Step 5: Waiting for git to sync..." sleep 10 # Step 6: Verify patterns verify_chassis_patterns "$PROJECT_NAME" verify_design_system "$PROJECT_NAME" verify_service_patterns "$PROJECT_NAME" verify_nextjs_app "$PROJECT_NAME" # Step 7: Wait for CI pipeline print_header "Step 7: Waiting for CI pipeline" if ! wait_for_pipeline "$PROJECT_NAME"; then print_warning "Pipeline may have issues, continuing to check site..." fi # Step 8: Wait for site print_header "Step 8: Verifying site is accessible" if ! wait_for_site "$domain" 30 5 "$PROJECT_NAME"; then print_warning "Site not yet accessible (may still be deploying)" fi # Summary print_header "E2E Test Results" print_success "Project created: $PROJECT_NAME" print_success "Components added: $comp_count" echo "" echo "Site URL: https://$domain" echo "Git repo: https://git.threesix.ai/$(get_git_owner)/$PROJECT_NAME" echo "CI: https://ci.threesix.ai/$(get_git_owner)/$PROJECT_NAME" echo "" echo "Next steps:" echo " 1. Clone the repo and start developing features" echo " 2. Use pkg/httperror, pkg/app for chassis patterns" echo " 3. Use packages/ui, packages/layout for design system" echo " 4. Use packages/auth for authentication" echo " 5. Run ./scripts/generate-client.sh after OpenAPI changes" } check_status() { print_header "Project Status: $PROJECT_NAME" # Get project info echo "Project:" api_call GET "/project/$PROJECT_NAME" | jq '.data // .' echo "" # Get components echo "Components:" api_call GET "/projects/$PROJECT_NAME/components" | jq '.data // .' echo "" # Get latest pipelines echo "Latest Pipelines:" api_call GET "/projects/$PROJECT_NAME/pipelines" | jq '.data[:3] // .' } verify_patterns() { print_header "Verifying Patterns: $PROJECT_NAME" verify_chassis_patterns "$PROJECT_NAME" verify_design_system "$PROJECT_NAME" verify_service_patterns "$PROJECT_NAME" verify_nextjs_app "$PROJECT_NAME" print_header "Verification Complete" } teardown() { print_header "Tearing down: $PROJECT_NAME" local result result=$(api_call DELETE "/project/$PROJECT_NAME") echo "$result" | jq '.' echo "" print_success "Project deleted. Gitea repo preserved." } case "$COMMAND" in run) run_flow ;; status) check_status ;; verify-patterns) verify_patterns ;; teardown) teardown ;; *) echo "Unknown command: $COMMAND" echo "Valid commands: run, status, verify-patterns, teardown" exit 1 ;; esac