Files
clientflow_backend/test_clientflow_e2e.sh
2026-06-09 22:55:58 +01:00

408 lines
12 KiB
Bash
Executable File

#!/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