cfdi-pac/source/tests/tests_finkok.py

273 lines
8.3 KiB
Python
Raw Normal View History

2021-01-01 17:31:23 -06:00
#!/usr/bin/env python3
import base64
import datetime
2021-01-02 12:14:16 -06:00
import random
import string
2021-01-01 17:31:23 -06:00
import sys
import time
import unittest
import uuid
import lxml.etree as ET
from io import BytesIO
from pathlib import Path
sys.path.append('..')
from pycert import SATCertificate
from finkok import PACFinkok
NAME = 'finkok'
TEMPLATE_CFDI = """<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" LugarExpedicion="06850" Moneda="MXN" SubTotal="10000.00" TipoCambio="1" TipoDeComprobante="I" Total="11600.00" FormaPago="01" MetodoPago="PUE" Version="3.3" xsi:schemaLocation="http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv33.xsd">
<cfdi:Emisor Rfc="EKU9003173C9" RegimenFiscal="601"/>
<cfdi:Receptor Rfc="BASM740115RW0" UsoCFDI="G01"/>
<cfdi:Conceptos>
<cfdi:Concepto Cantidad="1.0" ClaveProdServ="60121001" ClaveUnidad="ACT" Descripcion="Asesoría en desarrollo" Importe="10000.00" ValorUnitario="10000.00">
<cfdi:Impuestos>
<cfdi:Traslados>
<cfdi:Traslado Base="10000.00" Importe="1600.00" Impuesto="002" TasaOCuota="0.160000" TipoFactor="Tasa"/>
</cfdi:Traslados>
</cfdi:Impuestos>
</cfdi:Concepto>
</cfdi:Conceptos>
<cfdi:Impuestos TotalImpuestosTrasladados="1600.00">
<cfdi:Traslados>
<cfdi:Traslado Importe="1600.00" Impuesto="002" TasaOCuota="0.160000" TipoFactor="Tasa"/>
</cfdi:Traslados>
</cfdi:Impuestos>
</cfdi:Comprobante>
"""
TEMPLATE_CANCEL = """<Cancelacion RfcEmisor="{rfc}" Fecha="{fecha}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://cancelacfd.sat.gob.mx">
<Folios>
<UUID>{uuid}</UUID>
</Folios>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue />
</Reference>
</SignedInfo>
<SignatureValue />
<KeyInfo>
<X509Data>
<X509SubjectName />
<X509IssuerSerial />
<X509Certificate />
</X509Data>
<KeyValue />
</KeyInfo>
</Signature>
</Cancelacion>
"""
class TestCfdi(object):
def __init__(self):
self._xml = ''
self._make_cfdi()
@property
def xml(self):
return self._xml.decode()
def _make_cfdi(self):
path = Path(__file__)
path_cer = Path(path.parent).joinpath('certificados', f'{NAME}.cer')
path_key = Path(path.parent).joinpath('certificados', f'{NAME}.enc')
path_xslt = Path(path.parent).joinpath('xslt', 'cadena.xslt')
self._cer_ori = cer = path_cer.read_bytes()
self._key_ori = key = path_key.read_bytes()
self._cert = SATCertificate(cer, key)
self._doc = ET.parse(BytesIO(TEMPLATE_CFDI.encode()))
self._root = self._doc.getroot()
self._root.attrib['Fecha'] = datetime.datetime.now().isoformat()[:19]
self._root.attrib['NoCertificado'] = self._cert.serial_number
self._root.attrib['Certificado'] = self._cert.cer_txt
self._add_stamp(path_xslt)
self._xml = ET.tostring(self._root,
pretty_print=True, doctype='<?xml version="1.0" encoding="utf-8"?>')
return
def _add_stamp(self, path_xslt):
xslt = open(path_xslt, 'rb')
transfor = ET.XSLT(ET.parse(xslt))
cadena = str(transfor(self._doc)).encode()
stamp = self._cert.sign(cadena)
self._root.attrib['Sello'] = stamp
xslt.close()
return
def sign_xml(self, template):
tree = ET.fromstring(template.encode())
tree = self._cert.sign_xml(tree)
xml = ET.tostring(tree).decode()
return xml
@property
def cert(self):
cer = base64.b64encode(self._cer_ori).decode()
key = base64.b64encode(self._key_ori).decode()
return key, cer
2021-01-01 22:44:27 -06:00
@property
def cer_pem(self):
return self._cert.cer_pem
@property
def key_pem(self):
return self._cert.key_pem
2021-01-01 17:31:23 -06:00
class TestStamp(unittest.TestCase):
def setUp(self):
print(f'In method: {self._testMethodName}')
self.pac = PACFinkok()
def test_cfdi_stamp(self):
cfdi = TestCfdi().xml
result = self.pac.stamp(cfdi)
cfdi_uuid = result['uuid']
self.assertFalse(bool(self.pac.error))
self.assertTrue(bool(uuid.UUID(cfdi_uuid)))
def test_cfdi_cancel(self):
expected = '201'
cfdi = TestCfdi()
result = self.pac.stamp(cfdi.xml)
2021-01-01 22:44:27 -06:00
cfdi_xml = result['xml']
cfdi_uuid = result['uuid']
2021-01-01 17:31:23 -06:00
self.assertFalse(bool(self.pac.error))
self.assertTrue(bool(uuid.UUID(cfdi_uuid)))
2021-01-01 22:44:27 -06:00
time.sleep(3)
2021-01-01 17:31:23 -06:00
info = {
2021-01-01 22:44:27 -06:00
'cer': cfdi.cer_pem,
'key': cfdi.key_pem,
2021-01-01 17:31:23 -06:00
}
2021-01-01 22:44:27 -06:00
result = self.pac.cancel(cfdi_xml, info)
2021-01-01 17:31:23 -06:00
self.assertFalse(bool(self.pac.error))
2021-01-01 22:44:27 -06:00
tree = ET.fromstring(result['acuse'].encode())
NS = {'s': 'http://schemas.xmlsoap.org/soap/envelope/'}
2021-01-02 09:25:17 -06:00
path = 'string(//s:Body/*/*/*[1]/*[1])'
cancel_uuid = tree.xpath(path, namespaces=NS)
path = 'string(//s:Body/*/*/*[1]/*[2])'
status = tree.xpath(path, namespaces=NS)
2021-01-01 17:31:23 -06:00
self.assertEqual(cfdi_uuid, cancel_uuid)
self.assertEqual(status, expected)
2021-01-01 22:44:27 -06:00
return
2021-01-01 17:31:23 -06:00
def test_cfdi_cancel_xml(self):
expected = '201'
cfdi = TestCfdi()
result = self.pac.stamp(cfdi.xml)
2021-01-01 22:44:27 -06:00
cfdi_uuid = result['uuid']
2021-01-01 17:31:23 -06:00
self.assertFalse(bool(self.pac.error))
self.assertTrue(bool(uuid.UUID(cfdi_uuid)))
NS_CFDI = {
'cfdi': 'http://www.sat.gob.mx/cfd/3',
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
}
2021-01-01 22:44:27 -06:00
tree = ET.fromstring(result['xml'].encode())
2021-01-01 17:31:23 -06:00
rfc_emisor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Emisor/@Rfc)',
namespaces=NS_CFDI)
time.sleep(1)
data = {
'rfc': rfc_emisor,
'fecha': datetime.datetime.now().isoformat()[:19],
'uuid': cfdi_uuid,
}
template = TEMPLATE_CANCEL.format(**data)
sign_xml = cfdi.sign_xml(template)
2021-01-01 22:44:27 -06:00
2021-01-02 12:14:16 -06:00
time.sleep(60)
2021-01-01 22:44:27 -06:00
result = self.pac.cancel_xml(sign_xml)
self.assertFalse(bool(self.pac.error))
2021-01-02 12:14:16 -06:00
tree = ET.fromstring(result['acuse'].encode())
NS = {'s': 'http://schemas.xmlsoap.org/soap/envelope/'}
path = 'string(//s:Body/*/*/*[1]/*[1])'
cancel_uuid = tree.xpath(path, namespaces=NS)
path = 'string(//s:Body/*/*/*[1]/*[2])'
status = tree.xpath(path, namespaces=NS)
2021-01-01 17:31:23 -06:00
2021-01-02 12:14:16 -06:00
self.assertEqual(cfdi_uuid, cancel_uuid)
2021-01-01 17:31:23 -06:00
self.assertEqual(status, expected)
2021-01-02 12:14:16 -06:00
class TestClient(unittest.TestCase):
def setUp(self):
print(f'In method: {self._testMethodName}')
self.pac = PACFinkok()
def _get_random_date(self):
cd = datetime.date.today()
sd = datetime.date(1950, 1, 1)
days = random.randint(1, (cd-sd).days)
nd = sd + datetime.timedelta(days=days)
return nd.strftime('%y%m%d')
def _get_random_rfc(self):
i = ''.join(random.sample(string.ascii_lowercase, 4))
d = self._get_random_date()
h = uuid.uuid4().hex[:3]
rfc = f'{i}{d}{h}'.upper()
return rfc
def test_client_add(self):
rfc = self._get_random_rfc()
result = self.pac.client_add(rfc)
self.assertTrue(result)
return
def test_client_get_token(self):
expected = 60
rfc = self._get_random_rfc()
result = self.pac.client_add(rfc)
self.assertTrue(result)
result = self.pac.client_get_token(rfc, f'{rfc}@test.com')
self.assertEqual(len(result), expected)
return
def test_client_balance(self):
expected = 0
rfc = self._get_random_rfc()
result = self.pac.client_add(rfc)
self.assertTrue(result)
result = self.pac.client_balance(rfc=rfc)
self.assertEqual(result, expected)
return
2021-01-01 17:31:23 -06:00
if __name__ == '__main__':
unittest.main()
2021-01-02 12:14:16 -06:00