#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" COMPOSE_FILE="${ROOT_DIR}/docker-compose.yml" BACKEND_URL="${BACKEND_URL:-http://localhost:8000}" WAIT_SECONDS="${WAIT_SECONDS:-60}" PYTHON_BIN="${PYTHON_BIN:-python3}" EDITOR_EMAIL="${EDITOR_EMAIL:-editor@example.com}" cleanup() { docker compose -f "${COMPOSE_FILE}" down --remove-orphans >/dev/null 2>&1 || true } request() { local method="$1" local path="$2" local body="${3:-}" if [[ -n "${body}" ]]; then curl -fsS -X "${method}" -H "Content-Type: application/json" \ -H "X-Demo-User-Email: ${EDITOR_EMAIL}" --data "${body}" "${BACKEND_URL}${path}" return fi curl -fsS -X "${method}" -H "X-Demo-User-Email: ${EDITOR_EMAIL}" "${BACKEND_URL}${path}" } wait_backend() { local deadline=$((SECONDS + WAIT_SECONDS)) while (( SECONDS < deadline )); do if curl -fsS "${BACKEND_URL}/health" >/dev/null 2>&1; then return 0 fi sleep 1 done echo "Timed out waiting for backend at ${BACKEND_URL}" >&2 return 1 } json_eval() { local expression="$1" "${PYTHON_BIN}" -c "import json,sys; print(${expression})" } cd "${ROOT_DIR}" trap cleanup EXIT docker compose -f "${COMPOSE_FILE}" up --build -d wait_backend site_id="$(request GET /api/sites | json_eval "json.load(sys.stdin)[0]['site']['id']")" article_body="$( SITE_ID="${site_id}" "${PYTHON_BIN}" - <<'PY' import json import os import uuid print(json.dumps({ "target_site_id": os.environ["SITE_ID"], "brief_description": f"Research smoke flow {uuid.uuid4()}", "working_title": "Research Smoke Flow", "content_type": "longform_guide", "primary_keyword": "research smoke flow", })) PY )" article_id="$(request POST /api/articles "${article_body}" | json_eval "json.load(sys.stdin)['article']['id']")" questions_json="$(request POST "/api/articles/${article_id}/boundary-questions/generate")" question_patch_commands="$( QUESTIONS_JSON="${questions_json}" "${PYTHON_BIN}" - <<'PY' import json import os for question in json.loads(os.environ["QUESTIONS_JSON"])["questions"]: if question["is_required"]: print(f"{question['id']}\tAccepted answer for {question['category']}.") PY )" while IFS=$'\t' read -r question_id answer; do [[ -z "${question_id}" ]] && continue patch_body="$(ANSWER="${answer}" "${PYTHON_BIN}" - <<'PY' import json import os print(json.dumps({"answer": os.environ["ANSWER"]})) PY )" request PATCH "/api/articles/${article_id}/boundary-questions/${question_id}" "${patch_body}" >/dev/null done <<< "${question_patch_commands}" request POST "/api/articles/${article_id}/boundary-questions/submit" >/dev/null plan_id="$(request POST "/api/articles/${article_id}/plan/generate" | json_eval "json.load(sys.stdin)['plan']['id']")" request POST "/api/articles/${article_id}/plans/${plan_id}/approve" >/dev/null research_json="$(request POST "/api/articles/${article_id}/research/start")" manifest_id="$(printf '%s' "${research_json}" | json_eval "json.load(sys.stdin)['manifest']['id']")" artifact_count="$(printf '%s' "${research_json}" | json_eval "len(json.load(sys.stdin)['manifest']['artifacts'])")" if (( artifact_count < 2 )); then echo "Expected at least 2 research artifacts, got ${artifact_count}" >&2 exit 1 fi research_list_json="$(request GET "/api/articles/${article_id}/research")" listed_id="$(printf '%s' "${research_list_json}" | json_eval "json.load(sys.stdin)['manifests'][0]['id']")" if [[ "${listed_id}" != "${manifest_id}" ]]; then echo "Expected manifest ${manifest_id}, got ${listed_id}" >&2 exit 1 fi echo "research flow smoke ok"