#!/usr/bin/env python3 import base64 import datetime 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 comerciodigital import PACComercioDigital NAME = 'comercio' TEMPLATE_CFDI = """ """ TEMPLATE_CANCEL = """ {uuid} """ 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='') 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 class TestStamp(unittest.TestCase): def setUp(self): print(f'In method: {self._testMethodName}') self.pac = PACComercioDigital() def test_cfdi_stamp(self): cfdi = TestCfdi().xml result = self.pac.stamp(cfdi) cfdi_uuid = self.pac.cfdi_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_uuid = self.pac.cfdi_uuid self.assertFalse(bool(self.pac.error)) self.assertTrue(bool(uuid.UUID(cfdi_uuid))) time.sleep(1) cert = cfdi.cert info = { 'key': cert[0], 'cer': cert[1], 'pass': '12345678a', 'tipo': 'cfdi3.3', } result = self.pac.cancel(result, info) self.assertFalse(bool(self.pac.error)) tree = ET.fromstring(result) cancel_uuid = tree.xpath('string(//Acuse/Folios/UUID)') status = tree.xpath('string(//Acuse/Folios/EstatusUUID)') self.assertEqual(cfdi_uuid, cancel_uuid) self.assertEqual(status, expected) def test_cfdi_cancel_xml(self): expected = '201' cfdi = TestCfdi() result = self.pac.stamp(cfdi.xml) cfdi_uuid = self.pac.cfdi_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.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) info = { 'tipo': 'cfdi3.3', } result = self.pac.cancel_xml(result, sign_xml, info) tree = ET.fromstring(result) uid = tree.xpath('string(//Acuse/Folios/UUID)') status = tree.xpath('string(//Acuse/Folios/EstatusUUID)') self.assertEqual(cfdi_uuid, uid) self.assertEqual(status, expected) def test_cfdi_status(self): expected = '' cfdi = TestCfdi() result = self.pac.stamp(cfdi.xml) cfdi_uuid = self.pac.cfdi_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.encode()) rfc_emisor = tree.xpath( 'string(//cfdi:Comprobante/cfdi:Emisor/@Rfc)', namespaces=NS_CFDI) rfc_receptor = tree.xpath( 'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)', namespaces=NS_CFDI) total = tree.xpath( 'string(//cfdi:Comprobante/@Total)', namespaces=NS_CFDI) time.sleep(3) data = { 'rfc_receptor': rfc_receptor, 'rfc_emisor': rfc_emisor, 'total': total, 'uuid': cfdi_uuid, } result = self.pac.status(data) self.assertEqual(result, expected) if __name__ == '__main__': unittest.main()