Coverage for jbank/tests.py: 100%

167 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-27 13:36 +0700

1import os 

2import subprocess 

3from datetime import date, datetime, timedelta 

4from decimal import Decimal 

5from os.path import join 

6import pytz 

7import zeep 

8from django.conf import settings 

9from django.core.exceptions import ValidationError 

10from django.core.management import call_command 

11from django.template.loader import get_template 

12from django.test import TestCase 

13from django.utils.timezone import now 

14from jacc.models import Account 

15from jbank.csr_helpers import create_private_key, create_csr_pem, get_private_key_pem, strip_pem_header_and_footer 

16from jbank.ecb import parse_euro_exchange_rates_xml 

17from jbank.helpers import validate_xml, parse_date_or_relative_date 

18from jbank.models import WsEdiConnection, WsEdiSoapCall, Payout, PayoutParty, ReferencePaymentBatchFile, ReferencePaymentRecord 

19from jbank.tito import parse_tiliote_statements_from_file 

20from jbank.svm import parse_svm_batches_from_file 

21from jbank.sepa import Pain001, Pain002, PAIN001_REMITTANCE_INFO_OCR, PAIN001_REMITTANCE_INFO_OCR_ISO 

22from jbank.x509_helpers import get_x509_cert_from_file 

23from jutil.format import format_xml 

24from jutil.parse import parse_datetime 

25from jutil.validators import iban_bic 

26from lxml import etree # type: ignore # pytype: disable=import-error 

27from zeep.wsse import BinarySignature # type: ignore 

28 

29 

30class Tests(TestCase): 

31 def setUp(self): 

32 pass 

33 

34 def tearDown(self): 

35 pass 

36 

37 def test_pain001(self): 

38 debtor_acc = "FI4947300010416310" 

39 p = Pain001( 

40 "201802071211XJANITEST", 

41 "Vuokrahelppi", 

42 debtor_acc, 

43 iban_bic(debtor_acc), 

44 "020840699", 

45 ["Koukkukankareentie 29", "20320 Turku"], 

46 "FI", 

47 ) 

48 creditor_acc = "FI8847304720017517" 

49 p.add_payment("201802071339A0001", "Jani Kajala", creditor_acc, iban_bic(creditor_acc), Decimal("49.00"), "vuokratilitys") 

50 p.add_payment( 

51 "201802071339A0001", 

52 "Jani Kajala", 

53 creditor_acc, 

54 iban_bic(creditor_acc), 

55 Decimal("49.00"), 

56 "302300", 

57 PAIN001_REMITTANCE_INFO_OCR, 

58 ) 

59 p.add_payment( 

60 "201802071339A0001", 

61 "Jani Kajala", 

62 creditor_acc, 

63 iban_bic(creditor_acc), 

64 Decimal("49.00"), 

65 "RF92 1229", 

66 PAIN001_REMITTANCE_INFO_OCR_ISO, 

67 ) 

68 xml_str = format_xml(p.render_to_bytes().decode()) 

69 # print(xml_str) 

70 

71 filename = "/tmp/pain001.xml" 

72 with open(filename, "wt", encoding="utf-8") as fp: 

73 fp.write(xml_str) 

74 # print(filename, 'written') 

75 

76 # /usr/bin/xmllint --format --pretty 1 --load-trace --debug --schema $1 $2 

77 res = subprocess.run( 

78 [ 

79 "/usr/bin/xmllint", 

80 "--noout", 

81 # '--format', 

82 # '--pretty', '1', 

83 # '--load-trace', 

84 # '--debug', 

85 "--schema", 

86 join(settings.BASE_DIR, "data/pain001/pain.001.001.03.xsd"), 

87 filename, 

88 ] 

89 ) 

90 self.assertEqual(res.returncode, 0) 

91 

92 def test_to(self): 

93 filename = join(settings.BASE_DIR, "data/to/547404896.TO") 

94 statements = parse_tiliote_statements_from_file(filename) 

95 rec = statements[0]["records"][0] 

96 # pprint(rec) 

97 self.assertEqual(rec["amount"], Decimal("-1799.00")) 

98 self.assertEqual(rec["archive_identifier"], "180203473047IE5807") 

99 self.assertEqual(rec["paid_date"], date(2018, 2, 3)) 

100 self.assertEqual(rec["sepa"]["iban_account_number"], "FI8847304720017517") 

101 

102 def test_svm(self): 

103 filename = join(settings.BASE_DIR, "data/svm/547392460.SVM") 

104 batches = parse_svm_batches_from_file(filename) 

105 recs = batches[0]["records"] 

106 self.assertEqual(len(recs), 1) 

107 rec = recs[0] 

108 # pprint(rec) 

109 self.assertEqual(rec["amount"], Decimal("49.00")) 

110 self.assertEqual(rec["archive_identifier"], "02042588WWRV0212") 

111 self.assertEqual(rec["remittance_info"], "00000000000000013013") 

112 

113 def test_xp(self): 

114 filename = join(settings.BASE_DIR, "data/xp/547958656.XP") 

115 with open(filename, "rb") as fp: 

116 file_content = fp.read() 

117 p = Pain002(file_content) 

118 self.assertEqual(p.original_msg_id, "201802071211XJANITEST") 

119 self.assertEqual(p.msg_id, "V000000009726773") 

120 self.assertEqual(p.group_status, "ACCP") 

121 self.assertEqual(p.is_accepted, True) 

122 

123 def test_ecb_rates(self): 

124 filename = join(settings.BASE_DIR, "data/ecb-rates-2019-08-15.xml") 

125 with open(filename, "rt") as fp: 

126 content = fp.read() 

127 rates = parse_euro_exchange_rates_xml(content) 

128 # for record_date, currency, rate in rates[20:21]: 

129 # print(record_date, currency, rate) 

130 record_date, currency, rate = rates[20] 

131 self.assertEqual(record_date, date(2019, 8, 15)) 

132 self.assertEqual(currency, "HKD") 

133 self.assertEqual(rate, Decimal("8.744")) 

134 

135 def normalize_soap_env(self, content: bytes) -> bytes: 

136 doc = etree.fromstring(content) 

137 doc.find(".//{http://www.w3.org/2000/09/xmldsig#}DigestValue").text = "x" 

138 doc.find(".//{http://www.w3.org/2000/09/xmldsig#}Reference").attrib["URI"] = "x" 

139 doc.find(".//{http://www.w3.org/2000/09/xmldsig#}SignatureValue").text = "x" 

140 doc.find(".//{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}Reference").attrib["URI"] = "x" 

141 doc.find(".//{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}BinarySecurityToken").attrib[ 

142 "{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Id" 

143 ] = "x" 

144 doc.find(".//{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}BinarySecurityToken").text = "x" 

145 doc.find(".//{http://schemas.xmlsoap.org/soap/envelope/}Body").attrib[ 

146 "{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Id" 

147 ] = "x" 

148 return etree.tostring(doc) 

149 

150 def test_x509(self): 

151 print("MEDIA_ROOT = {}".format(settings.MEDIA_ROOT)) 

152 ws = WsEdiConnection( 

153 name="test", 

154 sender_identifier="12319203", 

155 receiver_identifier="123192031", 

156 target_identifier="1", 

157 environment="TEST", 

158 soap_endpoint="http://localhost", 

159 ) 

160 ws.signing_cert_file.name = "data/x509/cert.pem" 

161 ws.signing_key_file.name = "data/x509/key.pem" 

162 ws.save() 

163 cert = get_x509_cert_from_file("data/x509/cert.pem") 

164 not_valid_before, not_valid_after = pytz.utc.localize(cert.not_valid_before), pytz.utc.localize(cert.not_valid_after) 

165 self.assertEqual(not_valid_before, pytz.utc.localize(datetime(2019, 12, 3, 17, 54, 41))) 

166 self.assertEqual(not_valid_after, pytz.utc.localize(datetime(2019, 12, 13, 17, 54, 41))) 

167 self.assertEqual(WsEdiConnection.objects.get_by_receiver_identifier("123192031").id, ws.id) 

168 app = open("data/x509/appreq.xml", "rb").read() 

169 signed = ws.sign_application_request(app) 

170 ref_signed = b'<?xml version="1.0"?>\n<ApplicationRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://bxd.fi/xmldata/">\n <CustomerId>061133</CustomerId>\n <Timestamp>2012-02-20T08:50:59.4319012+01:00</Timestamp>\n <Environment>TEST</Environment>\n <FileReferences>\n <FileReference>1202170046-1202171334</FileReference>\n </FileReferences>\n <SoftwareId>DBSEPAClient</SoftwareId>\n <FileType>pain.002.001.02</FileType>\n <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">\n <SignedInfo>\n <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>\n <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>\n <Reference URI="">\n <Transforms>\n <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>\n </Transforms>\n <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>\n <DigestValue>D4Bg9OJdQHgsXMQM2ecyoqNwCn4=</DigestValue>\n </Reference>\n </SignedInfo>\n <SignatureValue>NwocqJ57CuLnQJaIA0Jm8JM2WlNOrNSM65Qt9nq69az4QMCgZBwRIGudZ+uMVWu0\n8TRriTHgrCTw5W50cVnhC/z+sw0mcNuYskXmGpr+4nUASYbcbT6EzktrHHkbhVTZ\nI4X4NdN8skU06TdgzF1Jvw/GzKENrm4l9hBLGbQyIaJppGmK1lN0g1suxnyvzgZK\ngFEEJZdFgFWcP+mCU+88FkcYOVjoX48rLtr0hSXyWRgUC9x3Seiw5GoS3toaY9dE\nmo6GREWUFEX9TjaH2sOy4WexiejlYisCyEGzBnyBGZ/Xi5EdoZSRoGrh60r+XDLX\n3Vu3vCJm5zD3SPX5fV9o/A==</SignatureValue>\n <KeyInfo>\n <X509Data>\n <X509IssuerSerial>\n <X509IssuerName>Issuer: {{ ws.signing_cert.issuer.rfc4514_string }}</X509IssuerName>\n <X509SerialNumber>{{ ws.signing_cert.serial_number }}</X509SerialNumber>\n </X509IssuerSerial>\n <X509Certificate>MIIDVDCCAjygAwIBAgIUGhIGnbdNdnLHN2Gb3GCgUeSJjQYwDQYJKoZIhvcNAQEL\nBQAwVzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlRYMQ8wDQYDVQQHDAZEYWxsYXMx\nFTATBgNVBAoMDEthamFsYSBHcm91cDETMBEGA1UEAwwKa2FqYWxhLmNvbTAeFw0x\nOTEyMDMxNzU0NDFaFw0xOTEyMTMxNzU0NDFaMFcxCzAJBgNVBAYTAlVTMQswCQYD\nVQQIDAJUWDEPMA0GA1UEBwwGRGFsbGFzMRUwEwYDVQQKDAxLYWphbGEgR3JvdXAx\nEzARBgNVBAMMCmthamFsYS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQDzpgdgMXiW9SOxnFPLdeFIltlCxH2rAz/nj7bLGK9ycVMMOW08M55jLhMK\nfOnyrHECWSP1aSKV6ZsNrRqm87JK8LEHgnIIDauqmJY4bEqB9nj7SzhAJWw5dJWN\nVNQto0csItaywXm0OjYRo4spDoKlyUbgXEcIouH4nWe91fvpvCfZiTHlfToTqKJy\nHtJ+kPi2+PSySs4+167g3A+v4YnoHgxTJD+q6KSimQQ0VGpqcV4Mt7+U3VV3Jb61\nSJNoanB31DLDugTjCOYm6PfT1/abQgPi7TOkizNa6HIMtMO/KFXC/6P+Bg05cQM9\n1Qq7iZR2zIg62AjC7z41xivdf8PdAgMBAAGjGDAWMBQGA1UdEQQNMAuCCWxvY2Fs\naG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAAn4D/QY8pdvbPYx+yDeYPlHnYv68ErBK\n7Ib2rrtM7jtumBVL9BCneacjdsLmsrXwNdQkQMynxl6bMa+uR3YkXyQSVs+aSwKy\nIkz++rI5ALRI5KQr/DGzWrrmlIbBrXtQkLUR2mnyw9t+ozSMPtdedVCr50c88B5j\ndnksF6odkXet2gpVa5aZ8T2HUl+DtixkKoQ66Ra0/cXXdi3pk6zfRAm8/wtVIzQY\nX2+rur2NOPUuCzdWAvNjzuWCgmH8A2BxCOWBAMJ/GpsVJpqJp0tgm7ah1N+r1y0c\nRqxXZw7bu3NXedB1YqmOvYRcAGK2WXH4aV7I/rDRCc+aMs1GCOocbw==</X509Certificate>\n </X509Data>\n </KeyInfo>\n </Signature>\n</ApplicationRequest>\n' 

171 self.assertEqual(signed, ref_signed) 

172 encoded = ws.encode_application_request(signed) 

173 ref_encoded = b"PEFwcGxpY2F0aW9uUmVxdWVzdCB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4c2Q9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxucz0iaHR0cDovL2J4ZC5maS94bWxkYXRhLyI+CiAgICA8Q3VzdG9tZXJJZD4wNjExMzM8L0N1c3RvbWVySWQ+CiAgICA8VGltZXN0YW1wPjIwMTItMDItMjBUMDg6NTA6NTkuNDMxOTAxMiswMTowMDwvVGltZXN0YW1wPgogICAgPEVudmlyb25tZW50PlRFU1Q8L0Vudmlyb25tZW50PgogICAgPEZpbGVSZWZlcmVuY2VzPgogICAgPEZpbGVSZWZlcmVuY2U+MTIwMjE3MDA0Ni0xMjAyMTcxMzM0PC9GaWxlUmVmZXJlbmNlPgogICAgPC9GaWxlUmVmZXJlbmNlcz4KICAgIDxTb2Z0d2FyZUlkPkRCU0VQQUNsaWVudDwvU29mdHdhcmVJZD4KICAgIDxGaWxlVHlwZT5wYWluLjAwMi4wMDEuMDI8L0ZpbGVUeXBlPgogICAgPFNpZ25hdHVyZSB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgICAgICAgPFNpZ25lZEluZm8+CiAgICAgICAgICA8Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPgogICAgICAgICAgPFNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPgogICAgICAgICAgPFJlZmVyZW5jZSBVUkk9IiI+CiAgICAgICAgICAgIDxUcmFuc2Zvcm1zPgogICAgICAgICAgICAgIDxUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPgogICAgICAgICAgICA8L1RyYW5zZm9ybXM+CiAgICAgICAgICAgIDxEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPgogICAgICAgICAgICA8RGlnZXN0VmFsdWU+RDRCZzlPSmRRSGdzWE1RTTJlY3lvcU53Q240PTwvRGlnZXN0VmFsdWU+CiAgICAgICAgICA8L1JlZmVyZW5jZT4KICAgICAgICA8L1NpZ25lZEluZm8+CiAgICAgICAgPFNpZ25hdHVyZVZhbHVlPk53b2NxSjU3Q3VMblFKYUlBMEptOEpNMldsTk9yTlNNNjVRdDlucTY5YXo0UU1DZ1pCd1JJR3VkWit1TVZXdTAKOFRScmlUSGdyQ1R3NVc1MGNWbmhDL3orc3cwbWNOdVlza1htR3ByKzRuVUFTWWJjYlQ2RXprdHJISGtiaFZUWgpJNFg0TmROOHNrVTA2VGRnekYxSnZ3L0d6S0VOcm00bDloQkxHYlF5SWFKcHBHbUsxbE4wZzFzdXhueXZ6Z1pLCmdGRUVKWmRGZ0ZXY1ArbUNVKzg4RmtjWU9Wam9YNDhyTHRyMGhTWHlXUmdVQzl4M1NlaXc1R29TM3RvYVk5ZEUKbW82R1JFV1VGRVg5VGphSDJzT3k0V2V4aWVqbFlpc0N5RUd6Qm55QkdaL1hpNUVkb1pTUm9Hcmg2MHIrWERMWAozVnUzdkNKbTV6RDNTUFg1ZlY5by9BPT08L1NpZ25hdHVyZVZhbHVlPgogICAgICAgIDxLZXlJbmZvPgogICAgICAgICAgPFg1MDlEYXRhPgogICAgICAgICAgICA8WDUwOUlzc3VlclNlcmlhbD4KICAgICAgICAgICAgICA8WDUwOUlzc3Vlck5hbWU+SXNzdWVyOiB7eyB3cy5zaWduaW5nX2NlcnQuaXNzdWVyLnJmYzQ1MTRfc3RyaW5nIH19PC9YNTA5SXNzdWVyTmFtZT4KICAgICAgICAgICAgICA8WDUwOVNlcmlhbE51bWJlcj57eyB3cy5zaWduaW5nX2NlcnQuc2VyaWFsX251bWJlciB9fTwvWDUwOVNlcmlhbE51bWJlcj4KICAgICAgICAgICAgPC9YNTA5SXNzdWVyU2VyaWFsPgogICAgICAgICAgICA8WDUwOUNlcnRpZmljYXRlPk1JSURWRENDQWp5Z0F3SUJBZ0lVR2hJR25iZE5kbkxITjJHYjNHQ2dVZVNKalFZd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1Z6RUxNQWtHQTFVRUJoTUNWVk14Q3pBSkJnTlZCQWdNQWxSWU1ROHdEUVlEVlFRSERBWkVZV3hzWVhNeApGVEFUQmdOVkJBb01ERXRoYW1Gc1lTQkhjbTkxY0RFVE1CRUdBMVVFQXd3S2EyRnFZV3hoTG1OdmJUQWVGdzB4Ck9URXlNRE14TnpVME5ERmFGdzB4T1RFeU1UTXhOelUwTkRGYU1GY3hDekFKQmdOVkJBWVRBbFZUTVFzd0NRWUQKVlFRSURBSlVXREVQTUEwR0ExVUVCd3dHUkdGc2JHRnpNUlV3RXdZRFZRUUtEQXhMWVdwaGJHRWdSM0p2ZFhBeApFekFSQmdOVkJBTU1DbXRoYW1Gc1lTNWpiMjB3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUR6cGdkZ01YaVc5U094bkZQTGRlRklsdGxDeEgyckF6L25qN2JMR0s5eWNWTU1PVzA4TTU1akxoTUsKZk9ueXJIRUNXU1AxYVNLVjZac05yUnFtODdKSzhMRUhnbklJRGF1cW1KWTRiRXFCOW5qN1N6aEFKV3c1ZEpXTgpWTlF0bzBjc0l0YXl3WG0wT2pZUm80c3BEb0tseVViZ1hFY0lvdUg0bldlOTFmdnB2Q2ZaaVRIbGZUb1RxS0p5Ckh0SitrUGkyK1BTeVNzNCsxNjdnM0ErdjRZbm9IZ3hUSkQrcTZLU2ltUVEwVkdwcWNWNE10NytVM1ZWM0piNjEKU0pOb2FuQjMxRExEdWdUakNPWW02UGZUMS9hYlFnUGk3VE9raXpOYTZISU10TU8vS0ZYQy82UCtCZzA1Y1FNOQoxUXE3aVpSMnpJZzYyQWpDN3o0MXhpdmRmOFBkQWdNQkFBR2pHREFXTUJRR0ExVWRFUVFOTUF1Q0NXeHZZMkZzCmFHOXpkREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBQW40RC9RWThwZHZiUFl4K3lEZVlQbEhuWXY2OEVyQksKN0liMnJydE03anR1bUJWTDlCQ25lYWNqZHNMbXNyWHdOZFFrUU15bnhsNmJNYSt1UjNZa1h5UVNWcythU3dLeQpJa3orK3JJNUFMUkk1S1FyL0RHeldycm1sSWJCclh0UWtMVVIybW55dzl0K296U01QdGRlZFZDcjUwYzg4QjVqCmRua3NGNm9ka1hldDJncFZhNWFaOFQySFVsK0R0aXhrS29RNjZSYTAvY1hYZGkzcGs2emZSQW04L3d0Vkl6UVkKWDIrcnVyMk5PUFV1Q3pkV0F2Tmp6dVdDZ21IOEEyQnhDT1dCQU1KL0dwc1ZKcHFKcDB0Z203YWgxTityMXkwYwpScXhYWnc3YnUzTlhlZEIxWXFtT3ZZUmNBR0syV1hINGFWN0kvckRSQ2MrYU1zMUdDT29jYnc9PTwvWDUwOUNlcnRpZmljYXRlPgogICAgICAgICAgPC9YNTA5RGF0YT4KICAgICAgICA8L0tleUluZm8+CiAgICA8L1NpZ25hdHVyZT4KPC9BcHBsaWNhdGlvblJlcXVlc3Q+Cg==" 

174 self.assertEqual(encoded, ref_encoded) 

175 timestamp = pytz.timezone("Europe/Helsinki").localize(datetime(2015, 2, 3, 14, 30)) 

176 soap_call = WsEdiSoapCall.objects.create(connection=ws, command="HelloWorld", created=timestamp) 

177 soap_body = get_template("jbank/soap_template.xml").render({"soap_call": soap_call, "payload": encoded.decode()}) 

178 body_bytes = soap_body.encode() 

179 envelope = etree.fromstring(body_bytes) 

180 binary_signature = BinarySignature(ws.signing_key_full_path, ws.signing_cert_full_path) 

181 soap_headers = {} 

182 envelope, soap_headers = binary_signature.apply(envelope, soap_headers) 

183 signed_body_bytes = etree.tostring(envelope) 

184 ref_bytes = b'<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://model.bxd.fi" xmlns:ns2="http://bxd.fi/CorporateFileService">\n <SOAP-ENV:Header><wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><Signature xmlns="http://www.w3.org/2000/09/xmldsig#">\n<SignedInfo>\n<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>\n<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>\n<Reference URI="#id-311224dc-3b4c-4729-b057-b3abb3bcd95d">\n<Transforms>\n<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>\n</Transforms>\n<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>\n<DigestValue>FGoo2ZirnTBuQ9N22TPlCO7eXaw=</DigestValue>\n</Reference>\n</SignedInfo>\n<SignatureValue>MNseos+F09uzlAH+1B1o01OBPslz80vJZWmi4yJ4NniZBXAD0rtPBRhbVwF4gSw1\nbLt1Eb6YKDl+nyRMb0f1H/QUuIibMXvlKu2IVYyTUktovW29oKkmXKfJtix9Eh+w\nzq4XEDZ3BjbkHW7FrS5ZZGRJB+IeePTHSZZ1JSxiBnOg5BQ0MlypUhdEaaHmJUM+\n7vSp/pttXc8vNdB/8pFCV0Yw/DrjANeNbFv2HXG0p1R1Dhmk2Tj3+cWDpWoFuWoj\nOg1y0eoTHW/5SSob9S9mEQPJi6aYr1ni89dF1I78o7kRDxK/JbyTNBq0YnLaXA2m\n6hJlwwfj9iYH3AkYe9YXsw==</SignatureValue>\n<KeyInfo>\n<wsse:SecurityTokenReference><wsse:Reference ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" URI="#id-e328e4ba-0ee6-4570-8476-89c88611e5ac"/></wsse:SecurityTokenReference></KeyInfo>\n</Signature><wsse:BinarySecurityToken xmlns:ns1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ns1:Id="id-e328e4ba-0ee6-4570-8476-89c88611e5ac">MIIDVDCCAjygAwIBAgIUGhIGnbdNdnLHN2Gb3GCgUeSJjQYwDQYJKoZIhvcNAQEL\nBQAwVzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlRYMQ8wDQYDVQQHDAZEYWxsYXMx\nFTATBgNVBAoMDEthamFsYSBHcm91cDETMBEGA1UEAwwKa2FqYWxhLmNvbTAeFw0x\nOTEyMDMxNzU0NDFaFw0xOTEyMTMxNzU0NDFaMFcxCzAJBgNVBAYTAlVTMQswCQYD\nVQQIDAJUWDEPMA0GA1UEBwwGRGFsbGFzMRUwEwYDVQQKDAxLYWphbGEgR3JvdXAx\nEzARBgNVBAMMCmthamFsYS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQDzpgdgMXiW9SOxnFPLdeFIltlCxH2rAz/nj7bLGK9ycVMMOW08M55jLhMK\nfOnyrHECWSP1aSKV6ZsNrRqm87JK8LEHgnIIDauqmJY4bEqB9nj7SzhAJWw5dJWN\nVNQto0csItaywXm0OjYRo4spDoKlyUbgXEcIouH4nWe91fvpvCfZiTHlfToTqKJy\nHtJ+kPi2+PSySs4+167g3A+v4YnoHgxTJD+q6KSimQQ0VGpqcV4Mt7+U3VV3Jb61\nSJNoanB31DLDugTjCOYm6PfT1/abQgPi7TOkizNa6HIMtMO/KFXC/6P+Bg05cQM9\n1Qq7iZR2zIg62AjC7z41xivdf8PdAgMBAAGjGDAWMBQGA1UdEQQNMAuCCWxvY2Fs\naG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAAn4D/QY8pdvbPYx+yDeYPlHnYv68ErBK\n7Ib2rrtM7jtumBVL9BCneacjdsLmsrXwNdQkQMynxl6bMa+uR3YkXyQSVs+aSwKy\nIkz++rI5ALRI5KQr/DGzWrrmlIbBrXtQkLUR2mnyw9t+ozSMPtdedVCr50c88B5j\ndnksF6odkXet2gpVa5aZ8T2HUl+DtixkKoQ66Ra0/cXXdi3pk6zfRAm8/wtVIzQY\nX2+rur2NOPUuCzdWAvNjzuWCgmH8A2BxCOWBAMJ/GpsVJpqJp0tgm7ah1N+r1y0c\nRqxXZw7bu3NXedB1YqmOvYRcAGK2WXH4aV7I/rDRCc+aMs1GCOocbw==</wsse:BinarySecurityToken></wsse:Security></SOAP-ENV:Header><SOAP-ENV:Body xmlns:ns0="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" ns0:Id="id-311224dc-3b4c-4729-b057-b3abb3bcd95d">\n <ns2:helloWorldin>\n <ns1:RequestHeader>\n <ns1:SenderId>12319203</ns1:SenderId>\n <ns1:RequestId>1</ns1:RequestId>\n <ns1:Timestamp>2015-02-03T14:30:00+02:00</ns1:Timestamp>\n <ns1:Language>FI</ns1:Language>\n <ns1:UserAgent>Kajala WS</ns1:UserAgent>\n <ns1:ReceiverId>123192031</ns1:ReceiverId>\n </ns1:RequestHeader>\n <ns1:ApplicationRequest>PEFwcGxpY2F0aW9uUmVxdWVzdCB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4c2Q9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxucz0iaHR0cDovL2J4ZC5maS94bWxkYXRhLyI+CiAgICA8Q3VzdG9tZXJJZD4wNjExMzM8L0N1c3RvbWVySWQ+CiAgICA8VGltZXN0YW1wPjIwMTItMDItMjBUMDg6NTA6NTkuNDMxOTAxMiswMTowMDwvVGltZXN0YW1wPgogICAgPEVudmlyb25tZW50PlRFU1Q8L0Vudmlyb25tZW50PgogICAgPEZpbGVSZWZlcmVuY2VzPgogICAgPEZpbGVSZWZlcmVuY2U+MTIwMjE3MDA0Ni0xMjAyMTcxMzM0PC9GaWxlUmVmZXJlbmNlPgogICAgPC9GaWxlUmVmZXJlbmNlcz4KICAgIDxTb2Z0d2FyZUlkPkRCU0VQQUNsaWVudDwvU29mdHdhcmVJZD4KICAgIDxGaWxlVHlwZT5wYWluLjAwMi4wMDEuMDI8L0ZpbGVUeXBlPgogICAgPFNpZ25hdHVyZSB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgICAgICAgPFNpZ25lZEluZm8+CiAgICAgICAgICA8Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPgogICAgICAgICAgPFNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPgogICAgICAgICAgPFJlZmVyZW5jZSBVUkk9IiI+CiAgICAgICAgICAgIDxUcmFuc2Zvcm1zPgogICAgICAgICAgICAgIDxUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPgogICAgICAgICAgICA8L1RyYW5zZm9ybXM+CiAgICAgICAgICAgIDxEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPgogICAgICAgICAgICA8RGlnZXN0VmFsdWU+RDRCZzlPSmRRSGdzWE1RTTJlY3lvcU53Q240PTwvRGlnZXN0VmFsdWU+CiAgICAgICAgICA8L1JlZmVyZW5jZT4KICAgICAgICA8L1NpZ25lZEluZm8+CiAgICAgICAgPFNpZ25hdHVyZVZhbHVlPk53b2NxSjU3Q3VMblFKYUlBMEptOEpNMldsTk9yTlNNNjVRdDlucTY5YXo0UU1DZ1pCd1JJR3VkWit1TVZXdTAKOFRScmlUSGdyQ1R3NVc1MGNWbmhDL3orc3cwbWNOdVlza1htR3ByKzRuVUFTWWJjYlQ2RXprdHJISGtiaFZUWgpJNFg0TmROOHNrVTA2VGRnekYxSnZ3L0d6S0VOcm00bDloQkxHYlF5SWFKcHBHbUsxbE4wZzFzdXhueXZ6Z1pLCmdGRUVKWmRGZ0ZXY1ArbUNVKzg4RmtjWU9Wam9YNDhyTHRyMGhTWHlXUmdVQzl4M1NlaXc1R29TM3RvYVk5ZEUKbW82R1JFV1VGRVg5VGphSDJzT3k0V2V4aWVqbFlpc0N5RUd6Qm55QkdaL1hpNUVkb1pTUm9Hcmg2MHIrWERMWAozVnUzdkNKbTV6RDNTUFg1ZlY5by9BPT08L1NpZ25hdHVyZVZhbHVlPgogICAgICAgIDxLZXlJbmZvPgogICAgICAgICAgPFg1MDlEYXRhPgogICAgICAgICAgICA8WDUwOUlzc3VlclNlcmlhbD4KICAgICAgICAgICAgICA8WDUwOUlzc3Vlck5hbWU+SXNzdWVyOiB7eyB3cy5zaWduaW5nX2NlcnQuaXNzdWVyLnJmYzQ1MTRfc3RyaW5nIH19PC9YNTA5SXNzdWVyTmFtZT4KICAgICAgICAgICAgICA8WDUwOVNlcmlhbE51bWJlcj57eyB3cy5zaWduaW5nX2NlcnQuc2VyaWFsX251bWJlciB9fTwvWDUwOVNlcmlhbE51bWJlcj4KICAgICAgICAgICAgPC9YNTA5SXNzdWVyU2VyaWFsPgogICAgICAgICAgICA8WDUwOUNlcnRpZmljYXRlPk1JSURWRENDQWp5Z0F3SUJBZ0lVR2hJR25iZE5kbkxITjJHYjNHQ2dVZVNKalFZd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1Z6RUxNQWtHQTFVRUJoTUNWVk14Q3pBSkJnTlZCQWdNQWxSWU1ROHdEUVlEVlFRSERBWkVZV3hzWVhNeApGVEFUQmdOVkJBb01ERXRoYW1Gc1lTQkhjbTkxY0RFVE1CRUdBMVVFQXd3S2EyRnFZV3hoTG1OdmJUQWVGdzB4Ck9URXlNRE14TnpVME5ERmFGdzB4T1RFeU1UTXhOelUwTkRGYU1GY3hDekFKQmdOVkJBWVRBbFZUTVFzd0NRWUQKVlFRSURBSlVXREVQTUEwR0ExVUVCd3dHUkdGc2JHRnpNUlV3RXdZRFZRUUtEQXhMWVdwaGJHRWdSM0p2ZFhBeApFekFSQmdOVkJBTU1DbXRoYW1Gc1lTNWpiMjB3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUR6cGdkZ01YaVc5U094bkZQTGRlRklsdGxDeEgyckF6L25qN2JMR0s5eWNWTU1PVzA4TTU1akxoTUsKZk9ueXJIRUNXU1AxYVNLVjZac05yUnFtODdKSzhMRUhnbklJRGF1cW1KWTRiRXFCOW5qN1N6aEFKV3c1ZEpXTgpWTlF0bzBjc0l0YXl3WG0wT2pZUm80c3BEb0tseVViZ1hFY0lvdUg0bldlOTFmdnB2Q2ZaaVRIbGZUb1RxS0p5Ckh0SitrUGkyK1BTeVNzNCsxNjdnM0ErdjRZbm9IZ3hUSkQrcTZLU2ltUVEwVkdwcWNWNE10NytVM1ZWM0piNjEKU0pOb2FuQjMxRExEdWdUakNPWW02UGZUMS9hYlFnUGk3VE9raXpOYTZISU10TU8vS0ZYQy82UCtCZzA1Y1FNOQoxUXE3aVpSMnpJZzYyQWpDN3o0MXhpdmRmOFBkQWdNQkFBR2pHREFXTUJRR0ExVWRFUVFOTUF1Q0NXeHZZMkZzCmFHOXpkREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBQW40RC9RWThwZHZiUFl4K3lEZVlQbEhuWXY2OEVyQksKN0liMnJydE03anR1bUJWTDlCQ25lYWNqZHNMbXNyWHdOZFFrUU15bnhsNmJNYSt1UjNZa1h5UVNWcythU3dLeQpJa3orK3JJNUFMUkk1S1FyL0RHeldycm1sSWJCclh0UWtMVVIybW55dzl0K296U01QdGRlZFZDcjUwYzg4QjVqCmRua3NGNm9ka1hldDJncFZhNWFaOFQySFVsK0R0aXhrS29RNjZSYTAvY1hYZGkzcGs2emZSQW04L3d0Vkl6UVkKWDIrcnVyMk5PUFV1Q3pkV0F2Tmp6dVdDZ21IOEEyQnhDT1dCQU1KL0dwc1ZKcHFKcDB0Z203YWgxTityMXkwYwpScXhYWnc3YnUzTlhlZEIxWXFtT3ZZUmNBR0syV1hINGFWN0kvckRSQ2MrYU1zMUdDT29jYnc9PTwvWDUwOUNlcnRpZmljYXRlPgogICAgICAgICAgPC9YNTA5RGF0YT4KICAgICAgICA8L0tleUluZm8+CiAgICA8L1NpZ25hdHVyZT4KPC9BcHBsaWNhdGlvblJlcXVlc3Q+Cg==</ns1:ApplicationRequest>\n </ns2:helloWorldin>\n </SOAP-ENV:Body>\n</SOAP-ENV:Envelope>' 

185 self.assertEqual(self.normalize_soap_env(ref_bytes), self.normalize_soap_env(signed_body_bytes)) 

186 

187 def test_x509_data(self): 

188 with open(os.path.join(settings.BASE_DIR, "data/x509/x509data.xml"), "rb") as fp: 

189 xml_bytes = fp.read() 

190 x509_data = etree.fromstring(xml_bytes) 

191 cert = x509_data.find(etree.QName(zeep.ns.DS, "X509Certificate")) 

192 self.assertIsNotNone(cert) 

193 

194 def test_validate_xml(self): 

195 xml = os.path.join(settings.BASE_DIR, "data/finvoice/xsd-test.xml") 

196 xsd = os.path.join(settings.BASE_DIR, "data/finvoice/xsd-test.xsd") 

197 with open(xml, "rb") as fp: 

198 validate_xml(fp.read(), xsd) 

199 

200 def test_payout_validation(self): 

201 payer = PayoutParty.objects.all().first() 

202 recipient = PayoutParty.objects.all().last() 

203 connection = WsEdiConnection.objects.all().first() 

204 acc = Account.objects.all().first() 

205 p = Payout(connection=connection, payer=payer, recipient=recipient, messages="testi", account=acc) 

206 with self.assertRaisesMessage(ValidationError, "> 0"): 

207 p.full_clean() 

208 

209 def test_rsa_csr(self): 

210 pk = create_private_key() 

211 csr = create_csr_pem(pk, common_name="kajala.com", country_name="FI", organization_name="Kajala Group Ltd") 

212 self.assertEqual(csr.decode().split("\n")[0], "-----BEGIN CERTIFICATE REQUEST-----") 

213 pk_pem = get_private_key_pem(pk) 

214 self.assertEqual(pk_pem.decode().split("\n")[0], "-----BEGIN PRIVATE KEY-----") 

215 self.assertFalse(strip_pem_header_and_footer(pk_pem).startswith(b"-----BEGIN")) 

216 

217 def test_parse_xt(self): 

218 if os.path.isdir("./downloads/xt"): 

219 call_command("parse_xt", "downloads/xt", auto_create_accounts=True) 

220 if os.path.isdir("./downloads/svm"): 

221 call_command("parse_svm", "downloads/svm", auto_create_accounts=True) 

222 call_command("parse_xt", "data/xt", auto_create_accounts=True) 

223 call_command("parse_svm", "data/svm", auto_create_accounts=True) 

224 call_command("parse_to", "data/to", auto_create_accounts=True) 

225 

226 def test_parse_date_or_relative_date(self): 

227 tz = pytz.utc 

228 time_now = now().astimezone(tz) 

229 date_now = time_now.date() 

230 self.assertEqual(parse_date_or_relative_date("yesterday", tz=tz), date_now - timedelta(days=1)) 

231 self.assertEqual(parse_date_or_relative_date("prev_60d", tz=tz), date_now - timedelta(days=60)) 

232 self.assertEqual(parse_date_or_relative_date(date_now.isoformat(), tz=tz), date_now) 

233 

234 def test_parse_xm(self): 

235 ReferencePaymentBatchFile.objects.all().delete() 

236 call_command("parse_xm", "data/xm", verbose=True, auto_create_accounts=True) 

237 file = ReferencePaymentBatchFile.objects.all().order_by("-id").first() 

238 assert isinstance(file, ReferencePaymentBatchFile) 

239 self.assertEqual(file.get_total_amount(), Decimal("7048.00")) 

240 self.assertEqual(ReferencePaymentRecord.objects.filter(batch__file=file).count(), 4)