#!/bin/sh
#
# Prova as propriedades dos papéis criados por scripts/criar-papeis.sh (F1.9).
#
#   SENHA_APP='...' sh scripts/conferir-papeis.sh
#
# Sai com 1 se qualquer prova falhar, para poder virar portão.
#
# CADA PROVA É FALSIFICÁVEL. "O papel é somente leitura" não se demonstra
# lendo a definição dele: demonstra-se TENTANDO ESCREVER e exigindo que o
# banco recuse. Uma verificação que nunca poderia falhar não prova nada — foi
# assim que, na F1.8, uma conferência acabou aceitando a própria mensagem de
# erro como prova de sucesso.
set -e

PROJETO="${PROJETO:-franquiabv}"
BANCO_PG="${BANCO_PG:-franquiabv_dev}"
USUARIO_PG="${USUARIO_PG:-franquiabv}"

if [ -z "$SENHA_APP" ]; then
    echo "ABORTADO: defina SENHA_APP (a mesma usada em criar-papeis.sh)." >&2
    exit 1
fi

FALHAS=0

# O dono. Superusuário: enxerga tudo, e é por isso que ele NÃO deve ser quem a
# aplicação usa.
dono() {
    docker compose -p "$PROJETO" exec -T postgres \
        psql -U "$USUARIO_PG" -d "$BANCO_PG" -tA -c "$1" 2>&1 || true
}

# Conecta por TCP de propósito: é assim que a aplicação conecta, e o `trust`
# local do contêiner não vale aqui.
como() {
    docker compose -p "$PROJETO" exec -T -e PGPASSWORD="$2" postgres \
        psql -U "$1" -h localhost -d "$BANCO_PG" -tA -c "$3" 2>&1 | grep -v '^SET$' || true
}

app() { como bvip_app "$SENHA_APP" "$1"; }

ok()   { printf '   %-58s OK\n' "$1"; }
falha() { printf '   %-58s FALHOU\n' "$1"; echo "      $2"; FALHAS=$((FALHAS + 1)); }

# ---------------------------------------------------------------------------
echo "== O papel da aplicação não é superusuário =="
# Se fosse, o RLS seria ignorado e todo o resto deste arquivo seria teatro.
#
# ERAM DOIS PAPÉIS até 09/09/2026. `bvip_bi` saiu com o Power BI (D9), e com ele
# saíram as provas de leitura, de recusa de escrita e de isolamento do BI.

for papel in bvip_app; do
    R=$(dono "SELECT rolsuper OR rolbypassrls FROM pg_roles WHERE rolname='$papel';")

    if [ "$R" = "f" ]; then
        ok "$papel não é superusuário nem tem BYPASSRLS"
    else
        falha "$papel não é superusuário nem tem BYPASSRLS" "devolveu: $R"
    fi
done

# ---------------------------------------------------------------------------
echo
echo "== O segundo inquilino, para as provas de RLS =="
#
# As provas de RLS precisam de um segundo inquilino COM DADO. Sem ele, "não vê
# nada de outro inquilino" seria verdade por não haver o que ver -- exatamente o
# tipo de prova vazia que não prova.

# A linha de prova e uma COPIA de uma unidade real, com id e inquilino
# trocados. Monta-la a mao exigiria acertar 60 colunas NOT NULL, e erraria de
# novo na proxima coluna que o schema ganhar -- o %ROWTYPE acompanha sozinho.
dono "DELETE FROM unidades WHERE id = 999001;" >/dev/null
dono "DELETE FROM inquilinos WHERE id = 2;" >/dev/null

dono "INSERT INTO inquilinos (id, nome) VALUES (2, 'Inquilino de prova');" >/dev/null

dono "DO \$\$
DECLARE r unidades%ROWTYPE;
BEGIN
    SELECT * INTO r FROM unidades WHERE inquilino_id = 1 ORDER BY id LIMIT 1;
    r.id := 999001;
    r.inquilino_id := 2;
    INSERT INTO unidades SELECT r.*;
END
\$\$;" >/dev/null

VISIVEL=$(dono "SELECT count(*) FROM unidades WHERE inquilino_id = 2;")

if [ "$VISIVEL" -ge 1 ] 2>/dev/null; then
    ok "a linha do inquilino 2 existe ($VISIVEL, vista pelo dono)"
else
    falha "a linha do inquilino 2 existe" "o dono não a vê: $VISIVEL -- prova inválida"
fi

# ---------------------------------------------------------------------------
echo
echo "== O RLS vale para a aplicação =="
#
# Aqui está a diferença que justifica o arquivo inteiro: com `franquiabv`
# (superusuário) o RLS é ignorado; com bvip_app ele vale.

R=$(app "SELECT count(*) FROM unidades WHERE id = 999001;")

if [ "$R" = "0" ]; then
    ok "bvip_app também não vê o inquilino 2"
else
    case "$R" in
        *ERROR*) ok "bvip_app sem inquilino declarado FALHA ALTO (não devolve vazio)" ;;
        *)       falha "bvip_app também não vê o inquilino 2" "viu: $R" ;;
    esac
fi

# A contraprova: com o inquilino declarado, bvip_app LE normalmente. Sem esta
# linha, "bvip_app nao ve o inquilino 2" seria compativel com "bvip_app nao ve
# nada" -- uma politica cega demais passaria pelo teste.
R=$(app "SET app.inquilino_id='1'; SELECT count(*) FROM unidades;")

if [ "$R" -gt 0 ] 2>/dev/null; then
    ok "bvip_app COM inquilino declarado le ($R unidades)"
else
    falha "bvip_app COM inquilino declarado le" "devolveu: $R"
fi

R=$(app "SET app.inquilino_id='1'; SELECT count(*) FROM unidades WHERE id = 999001;")

if [ "$R" = "0" ]; then
    ok "bvip_app COM inquilino 1 nao ve a linha do inquilino 2"
else
    falha "bvip_app COM inquilino 1 nao ve a linha do inquilino 2" "viu: $R"
fi

R=$(dono "SELECT count(*) FROM unidades WHERE id = 999001;")

if [ "$R" = "1" ]; then
    ok "o dono superusuário VÊ (é a razão de a aplicação não usá-lo)"
else
    falha "o dono superusuário VÊ" "devolveu: $R"
fi

# ---------------------------------------------------------------------------
echo
echo "== Limpando a linha de prova =="
dono "DELETE FROM unidades WHERE id = 999001; DELETE FROM inquilinos WHERE id = 2;" >/dev/null
ok "removida"

echo
if [ "$FALHAS" -eq 0 ]; then
    echo "TUDO CONFERE."
    exit 0
fi

echo "$FALHAS PROVAS FALHARAM."
exit 1
