2026-06-29 09:10:05 +05:30

124 lines
4.3 KiB
Python

"""Unit tests — no live Odoo or DB connection required."""
from __future__ import annotations
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from mt_odoo_mcp.confirmation.manager import ConfirmationManager
from mt_odoo_mcp.audit.logger import _sanitize
# ─── Confirmation Manager ──────────────────────────────────────────────────────
def test_token_created():
mgr = ConfirmationManager()
pending = mgr.create_token("default", "project.task", 42, "Fix Bug")
assert pending.token.startswith("del_")
assert pending.model == "project.task"
assert pending.record_id == 42
def test_token_consumed():
mgr = ConfirmationManager()
pending = mgr.create_token("default", "res.partner", 99, "ACME Corp")
ok, err = mgr.consume_token(pending.token, "res.partner", 99)
assert ok is True
assert err == ""
def test_token_single_use():
mgr = ConfirmationManager()
pending = mgr.create_token("default", "sale.order", 7, "SO007")
mgr.consume_token(pending.token, "sale.order", 7)
ok, err = mgr.consume_token(pending.token, "sale.order", 7)
assert ok is False
assert "expired" in err.lower() or "invalid" in err.lower()
def test_token_wrong_model():
mgr = ConfirmationManager()
pending = mgr.create_token("default", "project.task", 10, "Task A")
ok, err = mgr.consume_token(pending.token, "sale.order", 10)
assert ok is False
assert "does not match" in err.lower()
def test_token_wrong_record_id():
mgr = ConfirmationManager()
pending = mgr.create_token("default", "project.task", 10, "Task A")
ok, _ = mgr.consume_token(pending.token, "project.task", 999)
assert ok is False
def test_invalid_token():
mgr = ConfirmationManager()
ok, _ = mgr.consume_token("del_notarealtoken", "project.task", 1)
assert ok is False
def test_list_pending():
mgr = ConfirmationManager()
mgr.create_token("default", "account.move", 55, "INV/2026/001")
assert any(p["model"] == "account.move" for p in mgr.list_pending())
def test_per_user_isolation():
"""Two ConfirmationManager instances must not share tokens."""
mgr_a = ConfirmationManager()
mgr_b = ConfirmationManager()
pending = mgr_a.create_token("default", "project.task", 1, "Task")
ok, _ = mgr_b.consume_token(pending.token, "project.task", 1)
assert ok is False # mgr_b has no knowledge of mgr_a's tokens
# ─── Audit Logger Sanitization ─────────────────────────────────────────────────
def test_sanitize_password():
assert _sanitize({"username": "admin", "password": "s3cret"})["password"] == "***"
def test_sanitize_api_key():
result = _sanitize({"api_key": "abc123", "model": "project.task"})
assert result["api_key"] == "***"
assert result["model"] == "project.task"
def test_sanitize_nested():
result = _sanitize({"instance": {"name": "prod", "api_key": "secret"}})
assert result["instance"]["api_key"] == "***"
assert result["instance"]["name"] == "prod"
def test_sanitize_list_passthrough():
result = _sanitize({"ids": [1, 2, 3], "token": "tok_abc"})
assert result["ids"] == [1, 2, 3]
assert result["token"] == "***"
def test_sanitize_plain():
data = {"model": "sale.order", "limit": 10}
assert _sanitize(data) == data
# ─── Auth Crypto ──────────────────────────────────────────────────────────────
def test_password_hash_verify():
from mt_odoo_mcp.auth.crypto import hash_password, verify_password
h = hash_password("mysecret123")
assert verify_password("mysecret123", h)
assert not verify_password("wrong", h)
def test_api_key_format():
from mt_odoo_mcp.auth.crypto import generate_api_key
full, key_hash, prefix = generate_api_key()
assert full.startswith("mtom_")
assert len(full) > 20
assert prefix == full[:12]
def test_api_key_verify():
from mt_odoo_mcp.auth.crypto import generate_api_key, verify_api_key
full, key_hash, _ = generate_api_key()
assert verify_api_key(full, key_hash)
assert not verify_api_key("mtom_wrong", key_hash)