#!/usr/bin/env bash # Repeatable B12 smoke harness for running the reused Chatwoot frontend against GoChat. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" CHATWOOT_DIR="${CHATWOOT_DIR:-$ROOT/reference/chatwoot}" LOG_DIR="${GOCHAT_SMOKE_LOG_DIR:-$ROOT/.tmp/frontend-smoke}" REPORT_PATH="${GOCHAT_SMOKE_REPORT:-$ROOT/docs/parity/frontend_smoke_report.md}" API_HOST="${GOCHAT_SMOKE_API_HOST:-127.0.0.1}" API_PORT="${GOCHAT_SMOKE_API_PORT:-3000}" FRONTEND_HOST="${GOCHAT_SMOKE_FRONTEND_HOST:-127.0.0.1}" FRONTEND_PORT="${GOCHAT_SMOKE_FRONTEND_PORT:-3036}" SEARCH_ENGINE="${GOCHAT_SMOKE_SEARCH_ENGINE:-meilisearch}" MEILI_HOST="${GOCHAT_SMOKE_MEILI_HOST:-http://127.0.0.1:7700}" MEILI_API_KEY="${GOCHAT_SMOKE_MEILI_API_KEY:-gochat_dev}" MODE="run" KEEP_ALIVE="true" usage() { cat <&2; usage; exit 2 ;; esac done backend_cmd=(env GOCHAT_ENV=development GOCHAT_SERVER_HOST="$API_HOST" GOCHAT_SERVER_PORT="$API_PORT" GOCHAT_SERVER_MODE=debug GOCHAT_SEARCH_ENGINE="$SEARCH_ENGINE" GOCHAT_SEARCH_HOST="$MEILI_HOST" GOCHAT_SEARCH_API_KEY="$MEILI_API_KEY" GOCHAT_CAPTAIN_ENABLED=false go run ./cmd/gochat serve) frontend_cmd=(env CHATWOOT_API_HOST="http://$API_HOST:$API_PORT" pnpm exec vite --host "$FRONTEND_HOST" --port "$FRONTEND_PORT") need() { if ! command -v "$1" >/dev/null 2>&1; then echo "missing required command: $1" >&2 return 1 fi } write_report() { local status="$1" local notes="$2" mkdir -p "$(dirname "$REPORT_PATH")" cat >"$REPORT_PATH" <&2; return 1; } test -f "$CHATWOOT_DIR/package.json" || { echo "missing Chatwoot package.json" >&2; return 1; } test -f "$ROOT/cmd/gochat/main.go" || { echo "missing GoChat server entrypoint cmd/gochat/main.go" >&2; return 1; } } wait_url() { local url="$1" local label="$2" for _ in $(seq 1 60); do if curl -fsS "$url" >/dev/null 2>&1; then echo "$label ready: $url" return 0 fi sleep 1 done echo "$label did not become ready: $url" >&2 return 1 } print_commands() { echo "Backend: ${backend_cmd[*]}" echo "Frontend: (cd $CHATWOOT_DIR && ${frontend_cmd[*]})" echo "API smoke: scripts/parity_frontend_smoke.sh --api-smoke" echo "Browser smoke: scripts/parity_frontend_smoke.sh --browser-smoke" echo "Enterprise smoke: scripts/parity_frontend_smoke.sh --enterprise-smoke" echo "Enterprise browser smoke: scripts/parity_frontend_smoke.sh --enterprise-browser-smoke" echo "Report: $REPORT_PATH" } tmp_file() { local file file="$(mktemp "$LOG_DIR/smoke.XXXXXX")" echo "$file" } json_value() { local file="$1" local expr="$2" node -e 'const fs = require("fs"); const data = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); const value = Function("data", "return " + process.argv[2])(data); if (value === undefined || value === null) process.exit(2); if (typeof value === "object") console.log(JSON.stringify(value)); else console.log(String(value));' "$file" "$expr" } json_assert() { local file="$1" local expr="$2" local label="$3" node -e 'const fs = require("fs"); const data = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); if (!Function("data", "return " + process.argv[2])(data)) { console.error("assertion failed: " + process.argv[3]); process.exit(1); }' "$file" "$expr" "$label" echo "ok: $label" } header_value() { local file="$1" local name="$2" awk -v key="${name,,}" 'BEGIN { FS=":" } { current=tolower($1); if (current == key) { sub(/^[[:space:]]+/, "", $2); sub(/\r$/, "", $2); print $2; exit } }' "$file" } authed_curl() { curl -fsS \ -H "access-token: $ACCESS_TOKEN" \ -H "client: $CLIENT" \ -H "uid: $UID_HEADER" \ -H "token-type: $TOKEN_TYPE" \ -H "Content-Type: application/json" \ "$@" } run_api_smoke() { mkdir -p "$LOG_DIR" local seed_file account_id inbox_id contact_id company_id conversation_display_id conversation_uuid seed_file="$(tmp_file)" if [[ "$RUN_SEED" == "true" ]]; then echo "seeding smoke data..." (cd "$ROOT" && env GOCACHE="${GOCACHE:-/tmp/gochat-gocache}" GOMODCACHE="${GOMODCACHE:-/tmp/gochat-gomodcache}" go run ./cmd/gochat seed) >"$seed_file" else cat >"$seed_file" <&2 return 1 fi body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/auth/validate_token" cp "$body" "$LOG_DIR/validate_token.json" json_assert "$body" 'data.payload && data.payload.success === true' "auth validate_token accepts frontend headers" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/profile" cp "$body" "$LOG_DIR/profile.json" json_assert "$body" 'data.email === "'"$admin_email"'"' "profile returns Chatwoot user serializer" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/inboxes" cp "$body" "$LOG_DIR/inboxes.json" json_assert "$body" 'Array.isArray(data.payload) && data.payload.some(inbox => Number(inbox.id) === Number("'"$inbox_id"'"))' "inbox list includes seeded widget inbox" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/conversations?status=open" cp "$body" "$LOG_DIR/conversations.json" json_assert "$body" 'data.data && Array.isArray(data.data.payload) && data.data.payload.length >= 1' "conversation list returns Chatwoot data payload" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/conversations/$conversation_display_id/messages" cp "$body" "$LOG_DIR/conversation_messages.json" json_assert "$body" 'data.payload && Array.isArray(data.payload) && data.payload.length >= 1' "conversation messages return payload" local message_body message_body="$(tmp_file)" node -e 'const fs = require("fs"); fs.writeFileSync(process.argv[1], JSON.stringify({ content: "B12 API smoke outgoing message", message_type: "outgoing", private: false }));' "$message_body" body="$(tmp_file)" authed_curl -X POST --data-binary "@$message_body" -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/conversations/$conversation_display_id/messages" cp "$body" "$LOG_DIR/message_create.json" json_assert "$body" 'data.content === "B12 API smoke outgoing message"' "message create accepts dashboard payload" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/contacts/$contact_id" cp "$body" "$LOG_DIR/contact.json" json_assert "$body" 'data.payload && Number(data.payload.id) === Number("'"$contact_id"'")' "contact show returns payload" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/companies/$company_id" cp "$body" "$LOG_DIR/company.json" json_assert "$body" 'data.payload && Number(data.payload.id) === Number("'"$company_id"'")' "company show returns payload" body="$(tmp_file)" curl -fsS -X POST -o "$body" "http://$API_HOST:$API_PORT/api/v1/widget/config?website_token=gochat-smoke-widget-token" cp "$body" "$LOG_DIR/widget_config.json" json_assert "$body" 'data.website_channel_config && data.website_channel_config.website_token === "gochat-smoke-widget-token" && data.contact && data.contact.pubsub_token' "widget config returns auth token" local widget_token widget_token="$(json_value "$body" 'data.contact.pubsub_token')" body="$(tmp_file)" curl -fsS -H "X-Auth-Token: $widget_token" -H "Content-Type: application/json" -X POST --data-binary '{"message":{"content":"B12 widget smoke message"}}' -o "$body" "http://$API_HOST:$API_PORT/api/v1/widget/messages" cp "$body" "$LOG_DIR/widget_message.json" json_assert "$body" 'data.content === "B12 widget smoke message" && data.conversation_id' "widget message create works" if [[ -n "$conversation_uuid" ]]; then body="$(tmp_file)" curl -fsS -o "$body" "http://$API_HOST:$API_PORT/public/api/v1/csat_survey/$conversation_uuid" cp "$body" "$LOG_DIR/public_csat.json" json_assert "$body" 'data.display_type === "emoji" && data.content' "public CSAT show returns survey payload" fi BOOT_BACKEND_RESULT="Passed /health during API smoke" \ AUTH_PROFILE_RESULT="Passed API smoke" \ INBOX_RESULT="Passed API smoke" \ CONVERSATION_RESULT="Passed API smoke" \ CRM_RESULT="Passed contact/company API smoke" \ WIDGET_RESULT="Passed API smoke" \ CSAT_RESULT="Passed public show API smoke" \ ENTERPRISE_RESULT="Pending B12.3 browser/API smoke" \ write_report "API smoke passed for reused Chatwoot frontend core paths; live browser smoke not run in this mode." "Command run: \`scripts/parity_frontend_smoke.sh --api-smoke\`. Logs and payload captures are under \`$LOG_DIR\`. B12.2 still needs browser navigation assertions for the reused Vite app, and B12.3 must add enterprise screen assertions." echo "api smoke passed; report written to $REPORT_PATH" } run_browser_smoke() { run_api_smoke wait_url "http://$FRONTEND_HOST:$FRONTEND_PORT" "Chatwoot Vite" GOCHAT_ROOT="$ROOT" \ CHATWOOT_DIR="$CHATWOOT_DIR" \ GOCHAT_SMOKE_LOG_DIR="$LOG_DIR" \ GOCHAT_SMOKE_API_HOST="$API_HOST" \ GOCHAT_SMOKE_API_PORT="$API_PORT" \ GOCHAT_SMOKE_FRONTEND_HOST="$FRONTEND_HOST" \ GOCHAT_SMOKE_FRONTEND_PORT="$FRONTEND_PORT" \ node "$ROOT/scripts/parity_frontend_browser_smoke.mjs" BOOT_BACKEND_RESULT="Passed /health during browser smoke" \ BOOT_FRONTEND_RESULT="Passed Vite readiness during browser smoke" \ AUTH_PROFILE_RESULT="Passed browser login plus API smoke" \ INBOX_RESULT="Passed API smoke" \ CONVERSATION_RESULT="Passed dashboard browser request plus API smoke" \ CRM_RESULT="Passed contact/company API smoke" \ WIDGET_RESULT="Passed API smoke" \ CSAT_RESULT="Passed public show API smoke" \ ENTERPRISE_RESULT="Pending B12.3 browser/API smoke" \ write_report "Browser smoke passed for reused Chatwoot login and dashboard boot; API smoke passed for core frontend paths." "Command run: \`scripts/parity_frontend_smoke.sh --browser-smoke\`. Browser report: \`$LOG_DIR/browser-smoke-report.json\`. B12.3 must add enterprise screen assertions." echo "browser smoke passed; report written to $REPORT_PATH" } run_enterprise_api_smoke() { run_api_smoke local seed_file account_id inbox_id conversation_id conversation_display_id conversation_uuid custom_role_id capacity_policy_id captain_assistant_id sla_policy_id seed_file="$LOG_DIR/seed.json" account_id="$(json_value "$seed_file" 'data.account_id')" inbox_id="$(json_value "$seed_file" 'data.inbox_id')" conversation_id="$(json_value "$seed_file" 'data.conversation_id')" conversation_display_id="$(json_value "$seed_file" 'data.conversation_display_id || 1')" conversation_uuid="$(json_value "$seed_file" 'data.conversation_uuid || ""')" custom_role_id="$(json_value "$seed_file" 'data.custom_role_id')" capacity_policy_id="$(json_value "$seed_file" 'data.capacity_policy_id')" captain_assistant_id="$(json_value "$seed_file" 'data.captain_assistant_id')" sla_policy_id="$(json_value "$seed_file" 'data.sla_policy_id')" local body request_body created_id csv_file body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/applied_slas?page=1&sla_policy_id=$sla_policy_id" cp "$body" "$LOG_DIR/enterprise_applied_slas.json" json_assert "$body" 'Array.isArray(data.payload) && data.meta && Number(data.meta.current_page) === 1' "SLA report list returns Chatwoot payload/meta" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/applied_slas/metrics?sla_policy_id=$sla_policy_id" cp "$body" "$LOG_DIR/enterprise_applied_sla_metrics.json" json_assert "$body" 'typeof data === "object" && data !== null' "SLA report metrics returns JSON" csv_file="$(tmp_file)" authed_curl -o "$csv_file" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/applied_slas/download?sla_policy_id=$sla_policy_id" cp "$csv_file" "$LOG_DIR/enterprise_applied_sla_download.csv" if ! grep -q "Conversation ID,SLA policy breached" "$csv_file"; then echo "assertion failed: SLA download returns Chatwoot CSV headers" >&2 return 1 fi echo "ok: SLA download returns Chatwoot CSV headers" if [[ -n "$conversation_uuid" ]]; then request_body="$(tmp_file)" node -e 'const fs = require("fs"); fs.writeFileSync(process.argv[1], JSON.stringify({ message: { submitted_values: [{ csat_survey_response: { rating: 5, feedback_message: "B12 enterprise smoke" } }] } }));' "$request_body" body="$(tmp_file)" curl -fsS -H "Content-Type: application/json" -X PATCH --data-binary "@$request_body" -o "$body" "http://$API_HOST:$API_PORT/public/api/v1/csat_survey/$conversation_uuid" cp "$body" "$LOG_DIR/enterprise_public_csat_submit.json" json_assert "$body" 'data.rating === 5 || (data.csat_survey_response && data.csat_survey_response.rating === 5)' "public CSAT submit creates account report data" fi body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/csat_survey_responses?rating=5" cp "$body" "$LOG_DIR/enterprise_csat_responses.json" json_assert "$body" 'Array.isArray(data)' "CSAT report list returns raw array" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/csat_survey_responses/metrics" cp "$body" "$LOG_DIR/enterprise_csat_metrics.json" json_assert "$body" 'Object.prototype.hasOwnProperty.call(data, "total_count") && Object.prototype.hasOwnProperty.call(data, "ratings_count")' "CSAT metrics returns report counters" csv_file="$(tmp_file)" authed_curl -o "$csv_file" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/csat_survey_responses/download" cp "$csv_file" "$LOG_DIR/enterprise_csat_download.csv" if ! grep -q "Conversation ID" "$csv_file"; then echo "assertion failed: CSAT download returns CSV headers" >&2 return 1 fi echo "ok: CSAT download returns CSV headers" request_body="$(tmp_file)" node -e 'const fs = require("fs"); fs.writeFileSync(process.argv[1], JSON.stringify({ name: "B12 Enterprise Smoke Automation", description: "B12.3 smoke", event_name: "conversation_created", active: true, conditions: [], actions: [] }));' "$request_body" body="$(tmp_file)" authed_curl -X POST --data-binary "@$request_body" -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/automation_rules" cp "$body" "$LOG_DIR/enterprise_automation_create.json" json_assert "$body" 'data.name === "B12 Enterprise Smoke Automation" && data.event_name === "conversation_created"' "automation rule create returns raw Chatwoot rule" created_id="$(json_value "$body" 'data.id')" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/automation_rules" cp "$body" "$LOG_DIR/enterprise_automation_list.json" json_assert "$body" 'data.payload && data.payload.some(rule => Number(rule.id) === Number("'$created_id'"))' "automation rule list returns created rule in payload" request_body="$(tmp_file)" node -e 'const fs = require("fs"); fs.writeFileSync(process.argv[1], JSON.stringify({ name: "B12 Enterprise Smoke Macro", visibility: "global", actions: [] }));' "$request_body" body="$(tmp_file)" authed_curl -X POST --data-binary "@$request_body" -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/macros" cp "$body" "$LOG_DIR/enterprise_macro_create.json" json_assert "$body" 'data.payload && data.payload.name === "B12 Enterprise Smoke Macro" && data.payload.visibility === "global"' "macro create returns Chatwoot payload" created_id="$(json_value "$body" 'data.payload.id')" request_body="$(tmp_file)" node -e 'const fs = require("fs"); fs.writeFileSync(process.argv[1], JSON.stringify({ conversation_ids: [Number(process.argv[2])] }));' "$request_body" "$conversation_display_id" body="$(tmp_file)" authed_curl -X POST --data-binary "@$request_body" -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/macros/$created_id/execute" cp "$body" "$LOG_DIR/enterprise_macro_execute.txt" echo "ok: macro execute returns success" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/custom_roles" cp "$body" "$LOG_DIR/enterprise_custom_roles.json" json_assert "$body" 'Array.isArray(data) && data.some(role => Number(role.id) === Number("'$custom_role_id'") && Array.isArray(role.permissions))' "custom role list returns seeded enterprise role" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/agent_capacity_policies" cp "$body" "$LOG_DIR/enterprise_capacity_policies.json" json_assert "$body" 'Array.isArray(data) && data.some(policy => Number(policy.id) === Number("'$capacity_policy_id'"))' "agent capacity list returns seeded policy" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/agent_capacity_policies/$capacity_policy_id/users" cp "$body" "$LOG_DIR/enterprise_capacity_users.json" json_assert "$body" 'Array.isArray(data)' "agent capacity users returns array" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/audit_logs" cp "$body" "$LOG_DIR/enterprise_audit_logs.json" json_assert "$body" 'data.audit_logs && Array.isArray(data.audit_logs) && data.audit_logs.length >= 1 && data.current_page === 1' "audit logs return enterprise Jbuilder list" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/captain/preferences" cp "$body" "$LOG_DIR/enterprise_captain_preferences.json" json_assert "$body" 'data.providers && data.models && data.features' "Captain preferences returns providers/models/features" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/captain/assistants" cp "$body" "$LOG_DIR/enterprise_captain_assistants.json" json_assert "$body" 'data.payload && data.payload.some(assistant => Number(assistant.id) === Number("'$captain_assistant_id'"))' "Captain assistants list returns seeded assistant" request_body="$(tmp_file)" node -e 'const fs = require("fs"); fs.writeFileSync(process.argv[1], JSON.stringify({ message: "B12 enterprise copilot smoke", assistant_id: Number(process.argv[2]), conversation_id: Number(process.argv[3]) }));' "$request_body" "$captain_assistant_id" "$conversation_id" body="$(tmp_file)" authed_curl -X POST --data-binary "@$request_body" -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/captain/copilot_threads" cp "$body" "$LOG_DIR/enterprise_copilot_thread_create.json" json_assert "$body" 'data.id && data.assistant && Number(data.assistant.id) === Number("'$captain_assistant_id'")' "Copilot thread create returns Chatwoot thread payload" created_id="$(json_value "$body" 'data.id')" body="$(tmp_file)" authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/captain/copilot_threads/$created_id/copilot_messages" cp "$body" "$LOG_DIR/enterprise_copilot_messages.json" json_assert "$body" 'data.payload && data.payload.length >= 1 && data.payload[0].copilot_thread' "Copilot thread messages return nested payload" BOOT_BACKEND_RESULT="Passed /health during enterprise smoke" \ AUTH_PROFILE_RESULT="Passed API smoke" \ INBOX_RESULT="Passed API smoke" \ CONVERSATION_RESULT="Passed API smoke" \ CRM_RESULT="Passed contact/company API smoke" \ WIDGET_RESULT="Passed API smoke" \ CSAT_RESULT="Passed public/account/download enterprise smoke" \ ENTERPRISE_RESULT="Passed enterprise API smoke for SLA, CSAT, automation, macros, audit, custom roles, capacity, Captain, and Copilot" \ write_report "Enterprise API smoke passed for reused Chatwoot enterprise paths; live enterprise browser navigation remains optional." "Command run: \`scripts/parity_frontend_smoke.sh --enterprise-smoke\`. Logs and payload captures are under \`$LOG_DIR\`. Next B12.3 browser work should load the enterprise screens through the reused Vite app." echo "enterprise smoke passed; report written to $REPORT_PATH" } run_enterprise_browser_smoke() { run_enterprise_api_smoke wait_url "http://$FRONTEND_HOST:$FRONTEND_PORT" "Chatwoot Vite" GOCHAT_ROOT="$ROOT" \ CHATWOOT_DIR="$CHATWOOT_DIR" \ GOCHAT_SMOKE_LOG_DIR="$LOG_DIR" \ GOCHAT_SMOKE_API_HOST="$API_HOST" \ GOCHAT_SMOKE_API_PORT="$API_PORT" \ GOCHAT_SMOKE_FRONTEND_HOST="$FRONTEND_HOST" \ GOCHAT_SMOKE_FRONTEND_PORT="$FRONTEND_PORT" \ node "$ROOT/scripts/parity_frontend_browser_smoke.mjs" --enterprise BOOT_BACKEND_RESULT="Passed /health during enterprise browser smoke" \ BOOT_FRONTEND_RESULT="Passed Vite readiness during enterprise browser smoke" \ AUTH_PROFILE_RESULT="Passed browser login plus API smoke" \ INBOX_RESULT="Passed API smoke" \ CONVERSATION_RESULT="Passed dashboard browser request plus API smoke" \ CRM_RESULT="Passed contact/company API smoke" \ WIDGET_RESULT="Passed API smoke" \ CSAT_RESULT="Passed CSAT API/download and browser route requests" \ ENTERPRISE_RESULT="Passed enterprise browser route requests for SLA, CSAT, automation, macros, audit, custom roles, capacity, Captain, and Copilot" \ write_report "Enterprise browser smoke passed for reused Chatwoot enterprise route requests; enterprise API smoke passed." "Command run: \`scripts/parity_frontend_smoke.sh --enterprise-browser-smoke\`. Browser report: \`$LOG_DIR/browser-smoke-report.json\`." echo "enterprise browser smoke passed; report written to $REPORT_PATH" } if [[ "$MODE" == "print" ]]; then print_commands exit 0 fi check_prereqs if [[ "$MODE" == "check" ]]; then env GOCACHE="${GOCACHE:-/tmp/gochat-gocache}" GOMODCACHE="${GOMODCACHE:-/tmp/gochat-gomodcache}" go test ./cmd/gochat -count=1 write_report "Harness readiness check passed; live frontend smoke not run in this mode." "Run \`scripts/parity_frontend_smoke.sh --boot-only\` after PostgreSQL, Redis, Meilisearch, and Chatwoot frontend dependencies are available." echo "check passed; report written to $REPORT_PATH" exit 0 fi if [[ "$MODE" == "api-smoke" ]]; then run_api_smoke exit 0 fi if [[ "$MODE" == "browser-smoke" ]]; then run_browser_smoke exit 0 fi if [[ "$MODE" == "enterprise-smoke" ]]; then run_enterprise_api_smoke exit 0 fi if [[ "$MODE" == "enterprise-browser-smoke" ]]; then run_enterprise_browser_smoke exit 0 fi mkdir -p "$LOG_DIR" trap 'jobs -pr | xargs -r kill 2>/dev/null || true' EXIT echo "starting GoChat backend..." (cd "$ROOT" && "${backend_cmd[@]}") >"$LOG_DIR/gochat.log" 2>&1 & wait_url "http://$API_HOST:$API_PORT/health" "GoChat" echo "starting reused Chatwoot frontend..." (cd "$CHATWOOT_DIR" && "${frontend_cmd[@]}") >"$LOG_DIR/chatwoot-vite.log" 2>&1 & wait_url "http://$FRONTEND_HOST:$FRONTEND_PORT" "Chatwoot Vite" BOOT_BACKEND_RESULT="Passed boot readiness" \ BOOT_FRONTEND_RESULT="Passed boot readiness" \ write_report "Boot smoke passed for backend and reused Chatwoot frontend." "This run verified process boot and HTTP readiness only. B12.2 must add browser/API path assertions and update the smoke matrix." echo "smoke boot passed; report written to $REPORT_PATH" if [[ "$KEEP_ALIVE" == "true" ]]; then echo "press Ctrl-C to stop both processes" wait fi