cfdi-pac/source/tests/tests_finkok.py

307 lines
9.3 KiB
Python

#!/usr/bin/env python3
import base64
import datetime
import random
import string
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
@property
def cer_pem(self):
return self._cert.cer_pem
@property
def key_pem(self):
return self._cert.key_pem
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)
cfdi_xml = result['xml']
cfdi_uuid = result['uuid']
self.assertFalse(bool(self.pac.error))
self.assertTrue(bool(uuid.UUID(cfdi_uuid)))
time.sleep(3)
info = {
'cer': cfdi.cer_pem,
'key': cfdi.key_pem,
}
result = self.pac.cancel(cfdi_xml, info)
self.assertFalse(bool(self.pac.error))
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)
self.assertEqual(cfdi_uuid, cancel_uuid)
self.assertEqual(status, expected)
return
def test_cfdi_cancel_xml(self):
expected = '201'
cfdi = TestCfdi()
result = self.pac.stamp(cfdi.xml)
cfdi_uuid = result['uuid']
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',
}
tree = ET.fromstring(result['xml'].encode())
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)
time.sleep(60)
result = self.pac.cancel_xml(sign_xml)
self.assertFalse(bool(self.pac.error))
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)
self.assertEqual(cfdi_uuid, cancel_uuid)
self.assertEqual(status, expected)
class TestClient(unittest.TestCase):
def setUp(self):
print(f'In method: {self._testMethodName}')
self.pac = PACFinkok()
self.RFC = 'MBS740115000'
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_add_timbres(self):
result = self.pac.client_balance(rfc=self.RFC)
expected = result + 10
result = self.pac.client_add_timbres(self.RFC, 10)
self.assertEqual(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
def test_client_set_status(self):
expected = True
result = self.pac.client_set_status(self.RFC, False)
self.assertTrue(result)
result = self.pac.client_set_status(self.RFC, True)
self.assertTrue(result)
return
def test_client_switch(self):
expected = True
result = self.pac.client_switch(self.RFC, False)
self.assertTrue(result)
result = self.pac.client_switch(self.RFC, True)
self.assertTrue(result)
return
def test_client_report_folios(self):
expected = 0
date_from = '2021-01-01T00:00:00'
date_to = '2021-02-01T00:00:00'
result = self.pac.client_report_folios(self.RFC, date_from, date_to)
self.assertEqual(result, expected)
return
if __name__ == '__main__':
unittest.main()