ligbox-ops-platform/projects/ops-desk/api/tests/test_client_activation_043.py
Ligbox Spec Hub c815a32bbc feat(desk): Spec 043 Fases 2–6 — activar conta FOSS/OpenPanel/Odoo
Orquestra create_client/order FOSS, bridge OpenPanel com metadata,
upsert res.partner Odoo, mail-bundle wizard e webhook order-activated.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 15:19:54 +00:00

150 lines
5.8 KiB
Python

"""Unit tests — Spec 043 client activation orchestration."""
from __future__ import annotations
import importlib.util
import sqlite3
import sys
import types
import unittest
from pathlib import Path
from unittest.mock import MagicMock, patch
API_ROOT = Path(__file__).resolve().parents[1]
def _load(name: str, rel_path: str):
path = API_ROOT / rel_path
spec = importlib.util.spec_from_file_location(name, path)
if spec is None or spec.loader is None:
raise ImportError(path)
mod = importlib.util.module_from_spec(spec)
sys.modules[name] = mod
spec.loader.exec_module(mod)
return mod
billing_store = _load("billing_store_043", "app/billing_store.py")
activation_mapper = _load("activation_mapper_043b", "app/activation_mapper.py")
# Stubs vm123 para evitar import FastAPI em ambiente de teste mínimo
_mock_foss = types.ModuleType("app.vm123.foss_client")
_mock_odoo = types.ModuleType("app.vm123.odoo_client")
_mock_op = types.ModuleType("app.vm123.openpanel_client")
_mock_wizard = types.ModuleType("app.vm123.wizard_client")
_mock_vm123 = types.ModuleType("app.vm123")
_mock_vm123.foss_client = _mock_foss
_mock_vm123.odoo_client = _mock_odoo
_mock_vm123.openpanel_client = _mock_op
_mock_vm123.wizard_client = _mock_wizard
for key, mod in {
"app": types.ModuleType("app"),
"app.vm123": _mock_vm123,
"app.vm123.foss_client": _mock_foss,
"app.vm123.odoo_client": _mock_odoo,
"app.vm123.openpanel_client": _mock_op,
"app.vm123.wizard_client": _mock_wizard,
"app.billing_store": billing_store,
"app.activation_mapper": activation_mapper,
}.items():
sys.modules.setdefault(key, mod)
sys.modules["app"].billing_store = billing_store
client_activation = _load("client_activation_043", "app/client_activation.py")
def _memory_conn():
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
billing_store.init_schema(conn)
return conn
def _seed_account(conn, domain: str = "acme.com.br") -> int:
billing_store.upsert_from_company_validated(
conn,
domain=domain,
session_id="sess-1",
ticket_id=99,
data={
"billing_state": "awaiting_billing_validation",
"company_profile": {
"legal_name": "ACME LTDA",
"trade_name": "ACME",
"tax_id": "00000000000191",
"email_billing": "fin@acme.com",
"manager_name": "João Silva",
"manager_email": "admin@acme.com.br",
"address": {"country": "BR", "city": "São Paulo", "postal_code": "01310-100"},
},
},
)
row = conn.execute("SELECT id FROM billing_accounts WHERE domain = ?", (domain,)).fetchone()
return int(row["id"])
class TestClientActivation043(unittest.TestCase):
def test_activate_full_happy_path(self):
_mock_foss.create_client = MagicMock(return_value={"client_id": 101, "created": True, "reused": False})
_mock_foss.create_order = MagicMock(return_value={"order_id": 55, "created": True})
_mock_op.bridge_configured = MagicMock(return_value=True)
_mock_op.provision_user_safe = MagicMock(return_value={"success": True, "created": True})
_mock_odoo.upsert_customer_partner = MagicMock(return_value={"partner_id": 88, "created": True})
_mock_wizard.provision_mail_bundle = MagicMock(
return_value={"provisioned": True, "mail_host": "mail.acme.com.br"}
)
conn = _memory_conn()
acc_id = _seed_account(conn)
result = client_activation.activate_billing_account(
conn,
acc_id,
plan_code="ligbox-mail-business",
activated_by="roger",
)
self.assertTrue(result["ok"])
self.assertEqual(result["steps"]["foss"]["client_id"], 101)
self.assertEqual(result["steps"]["foss"]["order_id"], 55)
self.assertEqual(result["steps"]["odoo"]["partner_id"], 88)
acc = billing_store.get_account(conn, acc_id)
self.assertEqual(acc["billing_state"], "billing_active")
self.assertTrue(acc["recurrence_active"])
self.assertEqual(acc["external_customer_id"], "101")
def test_activate_idempotent_foss_client(self):
_mock_foss.create_client = MagicMock()
_mock_foss.create_order = MagicMock(return_value={"order_id": 12})
_mock_op.bridge_configured = MagicMock(return_value=False)
_mock_odoo.upsert_customer_partner = MagicMock(return_value={"partner_id": 3, "created": False})
_mock_wizard.provision_mail_bundle = MagicMock(return_value={"skipped": True})
conn = _memory_conn()
acc_id = _seed_account(conn)
billing_store.patch_account(conn, acc_id, external_customer_id="77")
result = client_activation.activate_billing_account(
conn, acc_id, plan_code="ligbox-mail-business", activated_by="roger"
)
_mock_foss.create_client.assert_not_called()
self.assertTrue(result["steps"]["foss"]["reused"])
def test_activate_rejects_active_account(self):
conn = _memory_conn()
acc_id = _seed_account(conn)
billing_store.patch_account(conn, acc_id, recurrence_active=True, billing_state="billing_active")
with self.assertRaises(client_activation.ActivationError):
client_activation.activate_billing_account(
conn, acc_id, plan_code="ligbox-mail-business", activated_by="roger"
)
class TestBillingStore043(unittest.TestCase):
def test_merge_company_profile(self):
conn = _memory_conn()
acc_id = _seed_account(conn)
billing_store.merge_company_profile(conn, acc_id, {"openpanel_username": "acmex"})
acc = billing_store.get_account(conn, acc_id)
self.assertEqual(acc["company_profile"]["openpanel_username"], "acmex")
if __name__ == "__main__":
unittest.main()