#!/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 = """ """ 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 @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() 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 if __name__ == '__main__': unittest.main()