144 lines
5.9 KiB
Python
144 lines
5.9 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import base64
|
|
|
|
from contextlib import contextmanager
|
|
from lxml import etree
|
|
from unittest.mock import patch
|
|
|
|
from odoo.addons.account.tests.common import skip_unless_external
|
|
from odoo.exceptions import RedirectWarning
|
|
|
|
from odoo.addons.l10n_ar.tests.common import TestArCommon
|
|
from odoo.tools.misc import file_open
|
|
from odoo.tools.zeep.exceptions import Fault
|
|
from odoo.tests import tagged
|
|
|
|
|
|
@tagged('post_install_l10n', 'post_install', '-at_install', *TestArCommon.extra_tags)
|
|
class TestArConnection(TestArCommon):
|
|
|
|
@contextmanager
|
|
def mock_zeep_client(self, response, should_error=False):
|
|
def create_fault_from_xml(xml_string):
|
|
# Parse the XML string into an etree Element
|
|
root = etree.fromstring(xml_string.encode())
|
|
|
|
# Extract fault information from the XML
|
|
# Typically, you need the faultcode and faultstring
|
|
faultcode = root.find(".//faultcode").text if root.find(".//faultcode") is not None else "Client"
|
|
faultstring = root.find(".//faultstring").text if root.find(".//faultstring") is not None else "Unknown error"
|
|
|
|
# Create a Fault exception
|
|
return Fault(
|
|
message=faultstring,
|
|
code=faultcode,
|
|
actor=None, # Optional
|
|
detail=root # You can pass the entire element as detail
|
|
)
|
|
|
|
class MockedService:
|
|
def __init__(self, response):
|
|
def create_endpoint(endpoint: str):
|
|
def call_endpoint(*args):
|
|
if should_error:
|
|
raise create_fault_from_xml(response)
|
|
return response
|
|
return call_endpoint
|
|
|
|
self.loginCms = create_endpoint('loginCms')
|
|
|
|
class MockedClient:
|
|
def __init__(self, wsdl, transport):
|
|
self.service = MockedService(response)
|
|
with patch('odoo.addons.l10n_ar_edi.models.l10n_ar_afipws_connection.Client', new=MockedClient):
|
|
yield
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.ar_private_key = cls.env['certificate.key'].create({
|
|
'name': 'AR Test Private key 1',
|
|
'content': base64.b64encode(
|
|
file_open("l10n_ar_edi/tests/private_key.pem", 'rb').read()
|
|
),
|
|
})
|
|
|
|
cls.ar_certificate_1 = cls.env['certificate.certificate'].create({
|
|
'name': 'AR Test certificate 1',
|
|
'content': base64.b64encode(
|
|
file_open("l10n_ar_edi/tests/test_cert1.crt", 'rb').read()
|
|
),
|
|
'private_key_id': cls.ar_private_key.id,
|
|
})
|
|
|
|
cls.company_ri.write({
|
|
'l10n_ar_afip_ws_environment': 'testing',
|
|
'l10n_ar_afip_ws_crt_id': cls.ar_certificate_1,
|
|
'l10n_ar_afip_ws_key_id': cls.ar_private_key.id,
|
|
})
|
|
cls.env.user.write({'company_id': cls.company_ri.id})
|
|
cls.config = cls.env['res.config.settings'].create({})
|
|
|
|
@skip_unless_external
|
|
def test_ar_connection_invalid(self):
|
|
""" With no key content set, they will appear as expired or invalid. """
|
|
|
|
private_content = self.ar_private_key.content
|
|
cert_content = self.ar_certificate_1.content
|
|
|
|
self.ar_certificate_1.content = ""
|
|
self.ar_private_key.content = ""
|
|
|
|
# Get the list of possible AR ARCA WebService connections. This allows us to know how many
|
|
# elements in the regex we need to repeat for the search.
|
|
conns_len = len(self.env['l10n_ar.afipws.connection']._get_l10n_ar_afip_ws())
|
|
error_content = rf"(.*Connection failed.*\n?){{{conns_len}}}"
|
|
|
|
with self.assertRaisesRegex(RedirectWarning, error_content):
|
|
self.config.with_context(l10n_ar_invoice_skip_commit=True).l10n_ar_connection_test()
|
|
|
|
login_response = """<?xml version="1.0" encoding="utf-8"?>
|
|
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
|
<soapenv:Body>
|
|
<soapenv:Fault>
|
|
<faultcode xmlns:ns1="http://xml.apache.org/axis/">ns1:cms.bad</faultcode>
|
|
<faultstring>El CMS no es valido</faultstring>
|
|
<detail>
|
|
<ns2:exceptionName xmlns:ns2="http://xml.apache.org/axis/">LoginFault</ns2:exceptionName>
|
|
<ns3:hostname xmlns:ns3="http://xml.apache.org/axis/">wsaaext0.homo.afip.gov.ar</ns3:hostname>
|
|
</detail>
|
|
</soapenv:Fault>
|
|
</soapenv:Body>
|
|
</soapenv:Envelope>
|
|
"""
|
|
|
|
error_content = rf"(.*El CMS no es valido.*\n?){{{conns_len}}}"
|
|
|
|
self.ar_private_key.content = private_content
|
|
self.ar_certificate_1.content = cert_content
|
|
|
|
with self.assertRaisesRegex(RedirectWarning, error_content):
|
|
with self.mock_zeep_client(login_response, should_error=True):
|
|
self.config.with_context(l10n_ar_invoice_skip_commit=True).l10n_ar_connection_test()
|
|
|
|
@skip_unless_external
|
|
def test_ar_connection_valid(self):
|
|
""" Patch the Zeep client so it doesn't make a network call and appears valid """
|
|
|
|
login_response = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
<loginTicketResponse version="1.0">
|
|
<credentials>
|
|
<token>dummy</token>
|
|
<sign>dummy</sign>
|
|
</credentials>
|
|
</loginTicketResponse>
|
|
"""
|
|
# Get the list of possible AR ARCA WebService connections. This allows us to know how many
|
|
# elements in the regex we need to repeat for the search.
|
|
conns_len = len(self.env['l10n_ar.afipws.connection']._get_l10n_ar_afip_ws())
|
|
error_content = rf"(.*Connection is available\n?){{{conns_len}}}"
|
|
|
|
with self.assertRaisesRegex(RedirectWarning, error_content):
|
|
with self.mock_zeep_client(login_response):
|
|
self.config.with_context(l10n_ar_invoice_skip_commit=True).l10n_ar_connection_test()
|