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>
150 lines
5.8 KiB
Python
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()
|