#!/usr/bin/env bash set -u set -o pipefail BASE_URL="${BASE_URL:-http://127.0.0.1:8020}" PGHOST="${PGHOST:-192.168.1.110}" PGUSER="${PGUSER:-clientflow}" PGDATABASE="${PGDATABASE:-clientflow}" TEST_TAG="e2e-$(date +%Y%m%d-%H%M%S)" CONTACT_ID="${TEST_TAG}-contact" PASS=0 FAIL=0 ok() { PASS=$((PASS+1)); echo "✅ $*"; } fail() { FAIL=$((FAIL+1)); echo "❌ $*"; } if [ -z "${PGPASSWORD:-}" ]; then echo "Define primeiro: export PGPASSWORD='password_da_bd'" exit 2 fi psql_q() { PGPASSWORD="$PGPASSWORD" psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -Atc "$1" } request() { NAME="$1" EXPECT="$2" shift 2 echo echo "==============================" echo "$NAME" echo "------------------------------" BODY="$(mktemp)" CODE="$(curl -sS -o "$BODY" -w "%{http_code}" "$@" || true)" echo "HTTP $CODE" if echo "$EXPECT" | grep -qw "$CODE"; then ok "$NAME" else fail "$NAME esperado [$EXPECT], recebeu HTTP $CODE" echo "Body:" cat "$BODY" echo fi LAST_BODY="$BODY" LAST_CODE="$CODE" } json_get() { FILE="$1" KEY="$2" python - "$FILE" "$KEY" <<'PY' import json, sys path, key = sys.argv[1], sys.argv[2] try: data = json.load(open(path, encoding="utf-8")) except Exception: print("") raise SystemExit cur = data for part in key.split("."): if isinstance(cur, dict): cur = cur.get(part) else: cur = None break print("" if cur is None else cur) PY } echo "============================================================" echo "ClientFlow E2E" echo "BASE_URL=$BASE_URL" echo "TEST_TAG=$TEST_TAG" echo "============================================================" echo echo "== 0. BD ==" DB_OK="$(psql_q "SELECT current_database() || ':' || current_user;")" if [ -n "$DB_OK" ]; then ok "BD OK: $DB_OK" else fail "Falha ligação BD" exit 2 fi for T in raw_events messages action_runs tasks opportunities opportunity_events products opportunity_items operation_links; do EXISTS="$(psql_q "SELECT to_regclass('public.$T') IS NOT NULL;")" if [ "$EXISTS" = "t" ]; then ok "Tabela existe: $T" else fail "Tabela em falta: $T" fi done PRODUCT_ID="$(psql_q "SELECT id FROM products WHERE active=true ORDER BY name LIMIT 1;")" if [ -n "$PRODUCT_ID" ]; then ok "Produto para teste: $PRODUCT_ID" else fail "Sem produto ativo para teste" fi echo echo "== 1. Páginas principais ==" request "GET /" "200" "$BASE_URL/" request "GET /tasks" "200" "$BASE_URL/tasks" request "GET /opportunities" "200" "$BASE_URL/opportunities" request "GET /products" "200" "$BASE_URL/products" request "GET /orders" "200" "$BASE_URL/orders" request "GET /finance" "200" "$BASE_URL/finance" request "GET /customers" "200" "$BASE_URL/customers" echo echo "== 2. Analyze SEND_INFO ==" request "POST /analyze SEND_INFO" "200" \ -X POST "$BASE_URL/analyze" \ -H "Content-Type: application/json" \ -d "{ \"last_customer_message\": \"Enviar informação detalhada sobre carregador.\", \"previous_context\": \"Assunto: Carregador\", \"source\": \"e2e_test\", \"conversation_id\": \"$TEST_TAG-info\", \"contact_id\": \"$CONTACT_ID\" }" TASK_INFO_ID="$(json_get "$LAST_BODY" "task_id")" if [ -z "$TASK_INFO_ID" ]; then TASK_INFO_ID="$(psql_q "SELECT id FROM tasks WHERE conversation_id='$TEST_TAG-info' ORDER BY created_at DESC LIMIT 1;")" fi if [ -n "$TASK_INFO_ID" ]; then ok "Task criada: $TASK_INFO_ID" else fail "Task SEND_INFO não criada" fi OPP_INFO_ID="$(psql_q "SELECT opportunity_id FROM tasks WHERE id::text='$TASK_INFO_ID' LIMIT 1;")" if [ -n "$OPP_INFO_ID" ]; then ok "Oportunidade criada/ligada: $OPP_INFO_ID" else fail "Sem oportunidade ligada à task" fi if [ -n "$TASK_INFO_ID" ]; then request "GET task criada" "200 302 303" "$BASE_URL/tasks/$TASK_INFO_ID" fi if [ -n "$OPP_INFO_ID" ]; then request "GET oportunidade criada" "200" "$BASE_URL/opportunities/$OPP_INFO_ID" fi echo echo "== 3. Produto na oportunidade ==" if [ -n "$OPP_INFO_ID" ] && [ -n "$PRODUCT_ID" ]; then request "POST adicionar produto" "302 303" \ -X POST "$BASE_URL/opportunities/$OPP_INFO_ID/items/add" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "product_id=$PRODUCT_ID" \ --data-urlencode "quantity=1" \ --data-urlencode "unit_price=" \ --data-urlencode "discount_amount=0" \ --data-urlencode "status=INTERESTED" ITEM_ID="$(psql_q "SELECT id FROM opportunity_items WHERE opportunity_id::text='$OPP_INFO_ID' ORDER BY created_at DESC LIMIT 1;")" if [ -n "$ITEM_ID" ]; then ok "Produto adicionado: $ITEM_ID" else fail "Produto não foi adicionado" fi fi echo echo "== 4. Stage oportunidade ==" if [ -n "$OPP_INFO_ID" ]; then request "POST stage INFO_SENT" "302 303" \ -X POST "$BASE_URL/opportunities/$OPP_INFO_ID/stage" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "stage=INFO_SENT" \ --data-urlencode "note=E2E informação enviada" STAGE="$(psql_q "SELECT stage FROM opportunities WHERE id::text='$OPP_INFO_ID' LIMIT 1;")" if [ "$STAGE" = "INFO_SENT" ]; then ok "Stage INFO_SENT confirmado" else fail "Stage esperado INFO_SENT, recebido $STAGE" fi fi echo echo "== 5. Complete task com nota ==" if [ -n "$TASK_INFO_ID" ]; then request "POST complete-with-note" "302 303" \ -X POST "$BASE_URL/tasks/$TASK_INFO_ID/complete-with-note" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "done_note=E2E tarefa concluída" \ --data-urlencode "done_note_extra=teste automático" STATUS="$(psql_q "SELECT status FROM tasks WHERE id::text='$TASK_INFO_ID' LIMIT 1;")" if [ "$STATUS" = "done" ]; then ok "Task concluída" else fail "Task deveria estar done, está $STATUS" fi fi echo echo "== 6. Cockpit operacional com workflow guard ==" if [ -n "$OPP_INFO_ID" ]; then echo echo "-- 6.1 Bloqueio esperado: pagamento antes de pró-forma --" request "POST operation payment_confirmed antes de pró-forma" "409" \ -X POST "$BASE_URL/opportunities/$OPP_INFO_ID/operations/payment_confirmed" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "note=E2E bloqueio esperado" echo echo "-- 6.2 Fluxo correto até venda Odoo --" request "POST operation jasmin_proforma" "302 303" \ -X POST "$BASE_URL/opportunities/$OPP_INFO_ID/operations/jasmin_proforma" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "external_id=$TEST_TAG-proforma" \ --data-urlencode "external_name=PF-$TEST_TAG" \ --data-urlencode "external_url=https://jasmin.blif.pt/e2e/proforma" \ --data-urlencode "note=E2E pró-forma emitida" request "POST operation payment_confirmed" "302 303" \ -X POST "$BASE_URL/opportunities/$OPP_INFO_ID/operations/payment_confirmed" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "note=E2E pagamento confirmado" request "POST operation odoo_sale_order" "302 303" \ -X POST "$BASE_URL/opportunities/$OPP_INFO_ID/operations/odoo_sale_order" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "external_id=$TEST_TAG-sale-order" \ --data-urlencode "external_name=SO-$TEST_TAG" \ --data-urlencode "external_url=https://odoo.plixin.com/web#id=999999&model=sale.order&view_type=form" \ --data-urlencode "note=E2E venda Odoo registada" echo echo "-- 6.3 Bloqueio esperado: Packlink antes de fatura/Odoo pronto --" request "POST operation packlink_shipment bloqueado" "409" \ -X POST "$BASE_URL/opportunities/$OPP_INFO_ID/operations/packlink_shipment" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "external_id=$TEST_TAG-packlink-blocked" \ --data-urlencode "external_name=PACKLINK-BLOQUEADO" \ --data-urlencode "note=E2E bloqueio packlink esperado" echo echo "-- 6.4 Simular Odoo pronto para despacho no teste --" PGPASSWORD="$PGPASSWORD" psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c " INSERT INTO operation_links ( opportunity_id, system, external_type, external_id, external_name, status, payload, last_synced_at, updated_at ) VALUES ( '$OPP_INFO_ID', 'odoo', 'physical_status', '$TEST_TAG-physical-ready', 'Estado físico E2E', 'ready_to_ship', jsonb_build_object( 'label', 'Pronta para despacho', 'reason', 'Simulação E2E: encomenda pronta.', 'next_action', 'Criar envio Packlink.', 'ready_to_ship', true, 'sale_order', jsonb_build_object('id', 999999, 'name', 'SO-' || '$TEST_TAG'), 'pickings', '[]'::jsonb, 'productions', '[]'::jsonb ), now(), now() ) ON CONFLICT (opportunity_id, system, external_type) DO UPDATE SET external_id = EXCLUDED.external_id, external_name = EXCLUDED.external_name, status = EXCLUDED.status, payload = EXCLUDED.payload, last_synced_at = now(), updated_at = now(); " request "POST operation odoo_physical_validated" "302 303" \ -X POST "$BASE_URL/opportunities/$OPP_INFO_ID/operations/odoo_physical_validated" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "external_id=$TEST_TAG-physical-validated" \ --data-urlencode "external_name=Validação física E2E" \ --data-urlencode "note=E2E encomenda física validada" request "POST operation jasmin_invoice" "302 303" \ -X POST "$BASE_URL/opportunities/$OPP_INFO_ID/operations/jasmin_invoice" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "external_id=$TEST_TAG-invoice" \ --data-urlencode "external_name=FT-$TEST_TAG" \ --data-urlencode "external_url=https://jasmin.blif.pt/e2e/invoice" \ --data-urlencode "note=E2E fatura emitida" request "POST operation packlink_shipment" "302 303" \ -X POST "$BASE_URL/opportunities/$OPP_INFO_ID/operations/packlink_shipment" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "external_id=$TEST_TAG-shipment" \ --data-urlencode "external_name=PACKLINK-$TEST_TAG" \ --data-urlencode "external_url=https://pro.packlink.pt/e2e/shipment" \ --data-urlencode "note=E2E envio criado" request "POST operation tracking_sent" "302 303" \ -X POST "$BASE_URL/opportunities/$OPP_INFO_ID/operations/tracking_sent" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "external_id=$TEST_TAG-tracking" \ --data-urlencode "external_name=TRK-$TEST_TAG" \ --data-urlencode "external_url=https://tracking.example/e2e" \ --data-urlencode "note=E2E tracking enviado" request "POST operation delivered" "302 303" \ -X POST "$BASE_URL/opportunities/$OPP_INFO_ID/operations/delivered" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "external_id=$TEST_TAG-delivered" \ --data-urlencode "external_name=Entrega E2E" \ --data-urlencode "note=E2E entregue" LINKS="$(psql_q "SELECT count(*) FROM operation_links WHERE opportunity_id::text='$OPP_INFO_ID';")" if [ "$LINKS" -ge 8 ]; then ok "operation_links criados: $LINKS" else fail "operation_links insuficientes: $LINKS" fi FINAL_STAGE="$(psql_q "SELECT stage FROM opportunities WHERE id::text='$OPP_INFO_ID' LIMIT 1;")" if [ "$FINAL_STAGE" = "WON" ]; then ok "Stage final WON" else fail "Stage final esperado WON, recebido $FINAL_STAGE" fi else fail "Sem OPP_INFO_ID; não foi possível testar cockpit operacional" fi echo "== 7. Resumo ==" psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c " SELECT action_code, status, count(*) FROM tasks GROUP BY action_code, status ORDER BY action_code, status; " psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c " SELECT stage, status, count(*) FROM opportunities GROUP BY stage, status ORDER BY stage, status; " psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -c " SELECT system, external_type, status, count(*) FROM operation_links GROUP BY system, external_type, status ORDER BY system, external_type, status; " echo echo "== 8. Limpeza ignorada para inspeção manual ==" echo echo "============================================================" echo "PASS=$PASS" echo "FAIL=$FAIL" echo "============================================================" if [ "$FAIL" -eq 0 ]; then echo "✅ E2E concluído sem falhas críticas" exit 0 else echo "❌ E2E encontrou falhas" exit 1 fi