408 lines
12 KiB
Bash
Executable File
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
|