"""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()