test: cover electronic invoicing permissions
This commit is contained in:
@@ -179,7 +179,7 @@ def require_purchase_invoice_create_access() -> None:
|
||||
return
|
||||
|
||||
frappe.throw(
|
||||
frappe._(
|
||||
_(
|
||||
"Vous n’êtes pas autorisé à créer "
|
||||
"une facture d’achat."
|
||||
),
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import frappe
|
||||
|
||||
from enuxia_einvoice import permissions
|
||||
|
||||
|
||||
def raise_frappe_exception(
|
||||
message,
|
||||
exc=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Reproduire frappe.throw sans contexte de site."""
|
||||
|
||||
exception = exc or RuntimeError
|
||||
raise exception(str(message))
|
||||
|
||||
|
||||
class PermissionTestCase(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
translation_patcher = patch.object(
|
||||
permissions,
|
||||
"_",
|
||||
side_effect=(
|
||||
lambda message, *args, **kwargs: message
|
||||
),
|
||||
)
|
||||
translation_patcher.start()
|
||||
self.addCleanup(
|
||||
translation_patcher.stop
|
||||
)
|
||||
|
||||
throw_patcher = patch.object(
|
||||
permissions.frappe,
|
||||
"throw",
|
||||
side_effect=raise_frappe_exception,
|
||||
)
|
||||
throw_patcher.start()
|
||||
self.addCleanup(
|
||||
throw_patcher.stop
|
||||
)
|
||||
|
||||
def get_access_level(
|
||||
self,
|
||||
*,
|
||||
user: str,
|
||||
roles: list[str],
|
||||
) -> int:
|
||||
with (
|
||||
patch.object(
|
||||
permissions.frappe,
|
||||
"session",
|
||||
SimpleNamespace(user=user),
|
||||
),
|
||||
patch.object(
|
||||
permissions.frappe,
|
||||
"get_roles",
|
||||
return_value=roles,
|
||||
),
|
||||
):
|
||||
return (
|
||||
permissions.get_einvoice_access_level()
|
||||
)
|
||||
|
||||
|
||||
class TestElectronicInvoiceRoles(
|
||||
PermissionTestCase
|
||||
):
|
||||
def test_role_hierarchy(self) -> None:
|
||||
cases = [
|
||||
([], 0),
|
||||
(
|
||||
[permissions.ROLE_READER],
|
||||
1,
|
||||
),
|
||||
(
|
||||
[permissions.ROLE_OPERATOR],
|
||||
2,
|
||||
),
|
||||
(
|
||||
[permissions.ROLE_MANAGER],
|
||||
3,
|
||||
),
|
||||
(
|
||||
[
|
||||
permissions.ROLE_READER,
|
||||
permissions.ROLE_MANAGER,
|
||||
],
|
||||
3,
|
||||
),
|
||||
]
|
||||
|
||||
for roles, expected in cases:
|
||||
with self.subTest(roles=roles):
|
||||
self.assertEqual(
|
||||
self.get_access_level(
|
||||
user="user@example.com",
|
||||
roles=roles,
|
||||
),
|
||||
expected,
|
||||
)
|
||||
|
||||
def test_administrator_bypass(self) -> None:
|
||||
self.assertEqual(
|
||||
self.get_access_level(
|
||||
user="Administrator",
|
||||
roles=[],
|
||||
),
|
||||
99,
|
||||
)
|
||||
|
||||
def test_system_manager_bypass(self) -> None:
|
||||
self.assertEqual(
|
||||
self.get_access_level(
|
||||
user="manager@example.com",
|
||||
roles=["System Manager"],
|
||||
),
|
||||
99,
|
||||
)
|
||||
|
||||
def test_require_role_accepts_level(
|
||||
self,
|
||||
) -> None:
|
||||
with patch.object(
|
||||
permissions,
|
||||
"get_einvoice_access_level",
|
||||
return_value=2,
|
||||
):
|
||||
permissions.require_einvoice_role(
|
||||
permissions.ROLE_OPERATOR
|
||||
)
|
||||
|
||||
def test_require_role_rejects_lower_level(
|
||||
self,
|
||||
) -> None:
|
||||
with patch.object(
|
||||
permissions,
|
||||
"get_einvoice_access_level",
|
||||
return_value=1,
|
||||
):
|
||||
with self.assertRaises(
|
||||
frappe.PermissionError
|
||||
):
|
||||
permissions.require_einvoice_role(
|
||||
permissions.ROLE_OPERATOR
|
||||
)
|
||||
|
||||
def test_require_role_rejects_unknown_role(
|
||||
self,
|
||||
) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
permissions.require_einvoice_role(
|
||||
"Rôle inconnu"
|
||||
)
|
||||
|
||||
|
||||
class TestCompanyAccess(
|
||||
PermissionTestCase
|
||||
):
|
||||
def test_empty_company_is_rejected(
|
||||
self,
|
||||
) -> None:
|
||||
self.assertFalse(
|
||||
permissions.has_company_access("")
|
||||
)
|
||||
|
||||
def test_privileged_user_bypasses_company_read(
|
||||
self,
|
||||
) -> None:
|
||||
with (
|
||||
patch.object(
|
||||
permissions,
|
||||
"get_einvoice_access_level",
|
||||
return_value=99,
|
||||
),
|
||||
patch.object(
|
||||
permissions.frappe,
|
||||
"get_doc",
|
||||
) as get_doc,
|
||||
):
|
||||
self.assertTrue(
|
||||
permissions.has_company_access(
|
||||
"Enuxia"
|
||||
)
|
||||
)
|
||||
|
||||
get_doc.assert_not_called()
|
||||
|
||||
def test_company_native_read_is_checked(
|
||||
self,
|
||||
) -> None:
|
||||
company = Mock()
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
permissions,
|
||||
"get_einvoice_access_level",
|
||||
return_value=2,
|
||||
),
|
||||
patch.object(
|
||||
permissions.frappe,
|
||||
"get_doc",
|
||||
return_value=company,
|
||||
),
|
||||
):
|
||||
self.assertTrue(
|
||||
permissions.has_company_access(
|
||||
"Enuxia"
|
||||
)
|
||||
)
|
||||
|
||||
company.check_permission.assert_called_once_with(
|
||||
"read"
|
||||
)
|
||||
|
||||
def test_company_permission_error_is_rejected(
|
||||
self,
|
||||
) -> None:
|
||||
company = Mock()
|
||||
company.check_permission.side_effect = (
|
||||
frappe.PermissionError
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
permissions,
|
||||
"get_einvoice_access_level",
|
||||
return_value=2,
|
||||
),
|
||||
patch.object(
|
||||
permissions.frappe,
|
||||
"get_doc",
|
||||
return_value=company,
|
||||
),
|
||||
):
|
||||
self.assertFalse(
|
||||
permissions.has_company_access(
|
||||
"Société interdite"
|
||||
)
|
||||
)
|
||||
|
||||
def test_require_company_access_raises(
|
||||
self,
|
||||
) -> None:
|
||||
with patch.object(
|
||||
permissions,
|
||||
"has_company_access",
|
||||
return_value=False,
|
||||
):
|
||||
with self.assertRaises(
|
||||
frappe.PermissionError
|
||||
):
|
||||
permissions.require_company_access(
|
||||
"Société interdite"
|
||||
)
|
||||
|
||||
|
||||
class TestDocumentAccess(
|
||||
PermissionTestCase
|
||||
):
|
||||
def test_exchange_access_composes_controls(
|
||||
self,
|
||||
) -> None:
|
||||
exchange = Mock()
|
||||
exchange.company = "Enuxia"
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
permissions,
|
||||
"require_einvoice_role",
|
||||
) as require_role,
|
||||
patch.object(
|
||||
permissions,
|
||||
"require_company_access",
|
||||
) as require_company,
|
||||
):
|
||||
permissions.require_exchange_access(
|
||||
exchange
|
||||
)
|
||||
|
||||
require_role.assert_called_once_with(
|
||||
permissions.ROLE_OPERATOR
|
||||
)
|
||||
exchange.check_permission.assert_called_once_with(
|
||||
"read"
|
||||
)
|
||||
require_company.assert_called_once_with(
|
||||
"Enuxia"
|
||||
)
|
||||
|
||||
def test_sales_invoice_access_composes_controls(
|
||||
self,
|
||||
) -> None:
|
||||
invoice = Mock()
|
||||
invoice.company = "Enuxia"
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
permissions,
|
||||
"require_einvoice_role",
|
||||
) as require_role,
|
||||
patch.object(
|
||||
permissions,
|
||||
"require_company_access",
|
||||
) as require_company,
|
||||
):
|
||||
permissions.require_sales_invoice_access(
|
||||
invoice,
|
||||
permission="read",
|
||||
minimum_role=(
|
||||
permissions.ROLE_READER
|
||||
),
|
||||
)
|
||||
|
||||
require_role.assert_called_once_with(
|
||||
permissions.ROLE_READER
|
||||
)
|
||||
invoice.check_permission.assert_called_once_with(
|
||||
"read"
|
||||
)
|
||||
require_company.assert_called_once_with(
|
||||
"Enuxia"
|
||||
)
|
||||
|
||||
def test_purchase_invoice_create_allowed(
|
||||
self,
|
||||
) -> None:
|
||||
with patch.object(
|
||||
permissions.frappe,
|
||||
"has_permission",
|
||||
return_value=True,
|
||||
) as has_permission:
|
||||
permissions.require_purchase_invoice_create_access()
|
||||
|
||||
has_permission.assert_called_once_with(
|
||||
"Purchase Invoice",
|
||||
ptype="create",
|
||||
)
|
||||
|
||||
def test_purchase_invoice_create_denied(
|
||||
self,
|
||||
) -> None:
|
||||
with patch.object(
|
||||
permissions.frappe,
|
||||
"has_permission",
|
||||
return_value=False,
|
||||
):
|
||||
with self.assertRaises(
|
||||
frappe.PermissionError
|
||||
):
|
||||
permissions.require_purchase_invoice_create_access()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user