Coverage for jutil/tests.py : 61%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import os
2from datetime import datetime, timedelta, date
3from decimal import Decimal
4from os.path import join
5from pprint import pprint
7import pytz
8from django.conf import settings
9from django.contrib.admin.models import LogEntry
10from django.contrib.auth.models import User
11from django.core.exceptions import ValidationError
12from django.core.management.base import CommandParser # type: ignore
13from django.db import models
14from django.test import TestCase
15from django.test.client import RequestFactory, Client
16from django.utils.translation import override, gettext as _, gettext_lazy
17from rest_framework.exceptions import NotAuthenticated
19from jutil.admin import admin_log, admin_obj_url, admin_obj_link, ModelAdminBase, AdminLogEntryMixin, \
20 AdminFileDownloadMixin
21from jutil.auth import require_auth, AuthUserMixin
22from jutil.command import get_date_range_by_name, add_date_range_arguments, parse_date_range_arguments
23from jutil.dict import dict_to_html, choices_label
24from jutil.email import make_email_recipient_list
25from jutil.model import is_model_field_changed, clone_model, get_model_field_label_and_value, get_object_or_none
26from jutil.request import get_ip_info
27from jutil.responses import FileSystemFileResponse
28from jutil.sftp import parse_sftp_connection
29from jutil.testing import DefaultTestSetupMixin
30from jutil.urls import url_equals, url_mod, url_host
31from jutil.xml import xml_to_dict, dict_to_element, _xml_filter_tag_name
32from jutil.dates import add_month, per_delta, per_month, this_week, next_month, next_week, this_month, last_month, \
33 last_year, last_week, yesterday, end_of_month
34from jutil.format import format_full_name, format_xml, format_xml_bytes, format_timedelta, dec1, dec2, dec3, dec4, dec5, \
35 dec6, format_table, ucfirst_lazy, strip_media_root, get_media_full_path, camel_case_to_underscore, \
36 underscore_to_camel_case
37from jutil.parse import parse_datetime, parse_bool
38from jutil.validators import fi_payment_reference_number, se_ssn_validator, se_ssn_filter, fi_iban_validator, \
39 se_iban_validator, iban_filter_readable, email_filter, iban_validator, iban_bank_info, fi_company_org_id_validator, \
40 email_validator, fi_payment_reference_validator, iso_payment_reference_validator, fi_ssn_age, \
41 se_clearing_code_bank_info, ascii_filter, ee_iban_validator, be_iban_validator, dk_iban_validator, \
42 dk_iban_bank_info, dk_clearing_code_bank_name, country_code_sanitizer, phone_sanitizer, email_sanitizer, \
43 fi_company_org_id_generator, phone_validator, passport_filter, passport_validator, passport_sanitizer, \
44 country_code_validator, validate_country_iban, iban_bic, validate_country_company_org_id, fi_ssn_generator, \
45 fi_ssn_validator, bic_validator, iban_generator
46from xml.etree.ElementTree import Element
47from xml.etree import ElementTree as ET
48from django.contrib import admin
51MY_CHOICE_1 = '1'
52MY_CHOICE_2 = '2'
53MY_CHOICES = (
54 (MY_CHOICE_1, 'MY_CHOICE_1'),
55 (MY_CHOICE_2, 'MY_CHOICE_2'),
56)
58request_factory = RequestFactory()
61def dummy_admin_func_a(modeladmin, request, qs):
62 print('dummy_admin_func_a')
63dummy_admin_func_a.short_description = 'A' # type: ignore
66def dummy_admin_func_b(modeladmin, request, qs):
67 print('dummy_admin_func_b')
68dummy_admin_func_b.short_description = 'B' # type: ignore
71class MyCustomAdmin(ModelAdminBase, AdminFileDownloadMixin):
72 max_history_length = 5
73 actions = (
74 dummy_admin_func_b,
75 dummy_admin_func_a,
76 )
78 def get_object(self, request, obj_id):
79 return self.model.objects.get(id=obj_id)
81 def get_object_by_filename(self, request, filename):
82 return User.objects.first() # dummy return for test_admin_file_download_mixin
85class Tests(TestCase, DefaultTestSetupMixin):
86 def setUp(self):
87 super().setUp()
88 user = self.add_test_user('test@example.com', 'test1234')
89 assert isinstance(user, User)
90 user.is_superuser = True
91 user.is_staff = True
92 user.save()
93 self.client = Client()
95 def tearDown(self):
96 super().setUp()
98 def test_payment_reference(self):
99 self.assertEqual(fi_payment_reference_number('100'), '1009')
101 def test_format_full_name(self):
102 samples = [
103 ('Short', 'Full Name', 'Short Full Name'),
104 ('Short Middle Name Is Quite Long', 'Full Name', 'Short Full Name'),
105 ('Short-Middle Name Is Quite Long', 'Full Name', 'Short Full Name'),
106 ('Olga Demi', 'Serpuhovitinova-Miettinen', 'Olga Serpuhovitinova'),
107 ('Olga-Anne Demi', 'Serpuhovitinovatsko', 'Olga S'),
108 ]
109 for v in samples: 109 ↛ 113line 109 didn't jump to line 113, because the loop on line 109 didn't complete
110 limited = format_full_name(v[0], v[1], 20)
111 # print('{} {} -> {} (was: {})'.format(v[0], v[1], v[2], limited))
112 self.assertEqual(v[2], limited)
113 try:
114 long_name = '19280309812083091829038190823081208301280381092830182038018203810283021'
115 format_full_name(long_name, long_name)
116 self.fail('format_full_name failed with long name')
117 except Exception:
118 pass
121 def test_add_month(self):
122 t = parse_datetime('2016-06-12T01:00:00')
123 self.assertTrue(isinstance(t, datetime))
124 assert isinstance(t, datetime)
125 self.assertEqual(t.isoformat(), '2016-06-12T01:00:00+00:00')
126 t2 = add_month(t)
127 self.assertEqual(t2.isoformat(), '2016-07-12T01:00:00+00:00')
128 t3 = add_month(t, 7)
129 self.assertEqual(t3.isoformat(), '2017-01-12T01:00:00+00:00')
130 t4 = add_month(t, -1)
131 self.assertEqual(t4.isoformat(), '2016-05-12T01:00:00+00:00')
132 time_now = datetime(2020, 6, 30, 15, 47, 23, 818646)
133 self.assertEqual(add_month(time_now, -4).isoformat(), '2020-02-29T15:47:23.818646')
134 self.assertEqual(add_month(time_now, 8).isoformat(), '2021-02-28T15:47:23.818646')
135 self.assertEqual(add_month(time_now, 0).isoformat(), '2020-06-30T15:47:23.818646')
137 def test_se_ssn(self):
138 se_ssn_validator('811228-9874')
139 se_ssn_validator('670919-9530')
140 with self.assertRaises(ValidationError):
141 se_ssn_validator('811228-9873')
143 def test_phone_numbers(self):
144 try:
145 phone_validator('+358456343767')
146 except Exception:
147 self.fail('phone_validator("+358456343767") should not raise Exception')
148 with self.assertRaisesMessage(ValidationError, _('Invalid phone number')):
149 phone_validator('214')
150 self.assertEqual(phone_sanitizer('214'), '')
152 def test_passport(self):
153 self.assertEqual(passport_filter('?ADsd-12312dsds'), 'ADSD-12312DSDS')
154 with self.assertRaisesMessage(ValidationError, _('Invalid passport number')):
155 passport_validator('214')
156 self.assertEqual(passport_sanitizer('214'), '')
158 def test_country_code(self):
159 with self.assertRaisesMessage(ValidationError, _('Invalid country code')):
160 country_code_validator('Finland')
162 def test_bic(self):
163 bic = iban_bic('FI21 1234 5600 0007 85')
164 self.assertEqual(bic, 'NDEAFIHH')
166 def test_org_id(self):
167 try:
168 validate_country_company_org_id('FI', '2084069-9')
169 except Exception:
170 self.fail('2084069-9 is valid org id')
171 with self.assertRaisesMessage(ValidationError, _('Invalid company organization ID')):
172 validate_country_company_org_id('FI', '2084069-8')
174 def test_validate_country_iban(self):
175 with self.assertRaisesMessage(ValidationError, _('Invalid IBAN account number')):
176 validate_country_iban('FI15616515616156', 'SE')
178 def test_fi_ssn_generator(self):
179 self.assertEqual(len(fi_ssn_generator()), 6+1+4)
180 for n in range(10): 180 ↛ exit, 180 ↛ 1812 missed branches: 1) line 180 didn't return from function 'test_fi_ssn_generator', because the loop on line 180 didn't complete, 2) line 180 didn't jump to line 181, because the loop on line 180 never started
181 ssn = fi_ssn_generator()
182 try:
183 fi_ssn_age(ssn)
184 fi_ssn_validator(ssn)
185 except Exception:
186 self.fail('{} is valid SSN'.format(ssn))
188 def test_iban(self):
189 with self.assertRaisesMessage(ValidationError, _('Invalid IBAN account number')):
190 iban_validator('FI2112345600000786')
191 bic_validator('HELSFIHH')
192 bic_validator('HELSFIHHXXX')
193 with self.assertRaisesMessage(ValidationError, _('Invalid bank BIC/SWIFT code')):
194 bic_validator('HELSFIH')
195 iban_validator('FI2112345600000785')
196 iban_validator('SE4550000000058398257466')
197 fi_iban_validator('FI2112345600000785')
198 se_iban_validator('SE4550000000058398257466')
199 ee_iban_validator('EE38 2200 2210 2014 5685')
200 be_iban_validator('BE68 5390 0754 7034')
201 with self.assertRaises(ValidationError):
202 fi_iban_validator('FI2112345600000784')
203 with self.assertRaises(ValidationError):
204 se_iban_validator('SE4550000000058398257465')
205 iban = 'FI8847304720017517'
206 self.assertEqual(iban_filter_readable(iban), 'FI88 4730 4720 0175 17')
208 def test_urls(self):
209 url = 'http://yle.fi/uutiset/3-8045550?a=123&b=456'
210 self.assertEqual(url_host(url), 'yle.fi')
211 self.assertTrue(url_equals('http://yle.fi/uutiset/3-8045550?a=123&b=456', 'http://yle.fi/uutiset/3-8045550?b=456&a=123'))
212 self.assertTrue(url_equals(url_mod('http://yle.fi/uutiset/3-8045550?a=123&b=456', {'b': '123', 'a': '456'}), 'http://yle.fi/uutiset/3-8045550?b=123&a=456'))
214 def test_email_filter_and_validation(self):
215 emails = [
216 (' Asdsa@a-a.com ', 'asdsa@a-a.com', True),
217 ('1asdsa@a-a2.com', '1asdsa@a-a2.com', True),
218 (' Asdsa@a-a ', 'asdsa@a-a', False),
219 (' @a-a2.com', '@a-a2.com', False),
220 (' a-a2.com', 'a-a2.com', False),
221 ('ää-a2@ää-a2.com', 'ää-a2@ää-a2.com', False),
222 ('aaa.bbbbb@ccc-ddd.fi', 'aaa.bbbbb@ccc-ddd.fi', True),
223 ]
224 for i, o, is_valid in emails: 224 ↛ exitline 224 didn't return from function 'test_email_filter_and_validation', because the loop on line 224 didn't complete
225 # print('email_filter({}) -> {}'.format(i, email_filter(i)))
226 self.assertEqual(email_filter(i), o)
227 if is_valid: 227 ↛ 228line 227 didn't jump to line 228, because the condition on line 227 was never true
228 email_validator(o)
229 else:
230 fail = False
231 try:
232 email_validator(o)
233 except ValidationError:
234 fail = True
235 self.assertTrue(fail, '{} is not valid email but passed validation'.format(o))
237 def test_ip_info(self):
238 ip, cc, host = get_ip_info('213.214.146.142')
239 self.assertEqual(ip, '213.214.146.142')
240 if cc: 240 ↛ 242line 240 didn't jump to line 242, because the condition on line 240 was never false
241 self.assertEqual(cc, 'FI')
242 if host: 242 ↛ exitline 242 didn't return from function 'test_ip_info', because the condition on line 242 was never false
243 self.assertEqual(host, '213214146142.edelkey.net')
245 def test_parse_xml(self):
246 # finvoice_201_example1.xml
247 xml_bytes = open(join(settings.BASE_DIR, 'data/fi/finvoice_201_example1.xml'), 'rb').read()
248 data = xml_to_dict(xml_bytes, value_key='value', attribute_prefix='_')
249 # pprint(data)
250 self.assertEqual(data['_Version'], '2.01')
251 self.assertEqual(data['InvoiceRow'][0]['ArticleIdentifier'], '12345')
252 self.assertEqual(data['InvoiceRow'][0]['DeliveredQuantity']['value'], '2')
253 self.assertEqual(data['InvoiceRow'][0]['DeliveredQuantity']['_QuantityUnitCode'], 'kpl')
254 self.assertEqual(data['InvoiceRow'][1]['ArticleIdentifier'], '123456')
256 # parse_xml1.xml
257 xml_str = open(join(settings.BASE_DIR, 'data/parse_xml1.xml'), 'rt').read()
258 data = xml_to_dict(xml_str.encode())
259 # pprint(data)
260 ref_data = {'@version': '1.2',
261 'A': [{'@class': 'x', 'B': {'@': 'hello', '@class': 'x2'}},
262 {'@class': 'y', 'B': {'@': 'world', '@class': 'y2'}}],
263 'C': 'value node'}
264 self.assertEqual(ref_data, data)
266 # parse_xml1.xml / no attributes
267 xml_str = open(join(settings.BASE_DIR, 'data/parse_xml1.xml'), 'rt').read()
268 data = xml_to_dict(xml_str.encode(), parse_attributes=False)
269 # pprint(data)
270 ref_data = {'A': [{'B': 'hello'}, {'B': 'world'}], 'C': 'value node'}
271 self.assertEqual(ref_data, data)
273 # parse_xml2.xml / no attributes
274 xml_str = open(join(settings.BASE_DIR, 'data/parse_xml2.xml'), 'rt').read()
275 data = xml_to_dict(xml_str.encode(), ['VastausLoki', 'LuottoTietoMerkinnat'], parse_attributes=False)
276 # pprint(data)
277 ref_data = {'VastausLoki': {'KysyttyHenkiloTunnus': '020685-1234',
278 'PaluuKoodi': 'Palveluvastaus onnistui',
279 'SyyKoodi': '1'}}
280 self.assertEqual(ref_data, data)
282 def test_dict_to_xml(self):
283 data = {
284 'Doc': {
285 '@version': '1.2',
286 'A': [{'@class': 'x', 'B': {'@': 'hello', '@class': 'x2'}},
287 {'@class': 'y', 'B': {'@': 'world', '@class': 'y2'}}],
288 'C': 'value node',
289 'D': 123,
290 'E': ['abc'],
291 }
292 }
293 el = dict_to_element(data)
294 assert isinstance(el, Element)
295 xml_str = ET.tostring(el, encoding='utf8', method='xml').decode()
296 # print(xml_str) # <Doc version="1.2"><C>value node</C><A class="x"><B class="x2">hello</B></A><A class="y"><B class="y2">world</B></A></Doc>
297 data2 = xml_to_dict(xml_str.encode(), document_tag=True, array_tags=['E'], int_tags=['D'])
298 # print('')
299 # pprint(data)
300 # pprint(data2)
301 self.assertEqual(data2, data)
303 def test_dict_to_xml2(self):
304 self.assertEqual(_xml_filter_tag_name('TagName[0]'), 'TagName')
305 self.assertEqual(_xml_filter_tag_name('TagName[1]'), 'TagName')
306 self.assertEqual(_xml_filter_tag_name('TagName'), 'TagName')
307 data = {
308 'Doc': {
309 '@version': '1.2',
310 'A': [{'@class': 'x', 'B': {'@': 'hello', '@class': 'x2'}},
311 {'@class': 'y', 'B': {'@': 'world', '@class': 'y2'}}],
312 'C': 'value node',
313 'D': 123,
314 'E': ['abc'],
315 'F': ['line 1', 'line 2']
316 }
317 }
318 el = dict_to_element(data)
319 assert isinstance(el, Element)
320 xml_str = ET.tostring(el, encoding='utf8', method='xml').decode()
321 data2 = xml_to_dict(xml_str.encode(), document_tag=True, array_tags=['E', 'F'], int_tags=['D'])
322 self.assertEqual(data2, data)
324 def test_xml_to_dict(self):
325 xml_str = """<?xml version="1.0" encoding="utf-8"?>
326<Document>
327 <TxsSummry>
328 <TtlNtries>
329 <NbOfNtries>12</NbOfNtries>
330 </TtlNtries>
331 <TtlCdtNtries>
332 <NbOfNtries>34</NbOfNtries>
333 <Sum>1234.56</Sum>
334 </TtlCdtNtries>
335 <TtlDbtNtries>
336 <NbOfNtries>0</NbOfNtries>
337 <Sum>0</Sum>
338 </TtlDbtNtries>
339 </TxsSummry>
340</Document>"""
341 data = xml_to_dict(xml_str.encode(), document_tag=True, array_tags=[], int_tags=['NbOfNtries'])
342 # print('')
343 # pprint(data)
344 self.assertEqual(data['Document']['TxsSummry']['TtlNtries']['NbOfNtries'], 12)
345 self.assertEqual(data['Document']['TxsSummry']['TtlCdtNtries']['NbOfNtries'], 34)
347 def test_per_delta(self):
348 begin = datetime(2017, 9, 17, 11, 42)
349 end = begin + timedelta(days=4)
350 ref = [(datetime(2017, 9, 17, 11, 42), datetime(2017, 9, 18, 11, 42)),
351 (datetime(2017, 9, 18, 11, 42), datetime(2017, 9, 19, 11, 42)),
352 (datetime(2017, 9, 19, 11, 42), datetime(2017, 9, 20, 11, 42)),
353 (datetime(2017, 9, 20, 11, 42), datetime(2017, 9, 21, 11, 42))]
354 res = per_delta(begin, end, timedelta(days=1))
355 self.assertEqual(list(res), ref)
357 def test_per_month(self):
358 begin = datetime(2017, 9, 1, 0, 0)
359 res = list(per_month(begin, begin+timedelta(days=32)))
360 ref = [(datetime(2017, 9, 1, 0, 0), datetime(2017, 10, 1, 0, 0)),
361 (datetime(2017, 10, 1, 0, 0), datetime(2017, 11, 1, 0, 0))]
362 self.assertEqual(list(res), ref)
364 def test_dates(self):
365 t = datetime(2018, 1, 30)
366 b, e = this_week(t)
367 self.assertEqual(b, pytz.utc.localize(datetime(2018, 1, 29)))
368 self.assertEqual(e, pytz.utc.localize(datetime(2018, 2, 5)))
369 b, e = this_month(t)
370 self.assertEqual(b, pytz.utc.localize(datetime(2018, 1, 1)))
371 self.assertEqual(e, pytz.utc.localize(datetime(2018, 2, 1)))
372 b, e = next_week(t)
373 self.assertEqual(b, pytz.utc.localize(datetime(2018, 2, 5)))
374 self.assertEqual(e, pytz.utc.localize(datetime(2018, 2, 12)))
376 def test_named_date_ranges(self):
377 t = datetime(2018, 5, 31)
378 t_tz = pytz.utc.localize(t)
379 named_ranges = [
380 ('last_month', last_month(t)),
381 ('last_year', last_year(t)),
382 ('this_month', this_month(t)),
383 ('last_week', last_week(t)),
384 ('yesterday', yesterday(t)),
385 ('today', yesterday(t + timedelta(hours=24))),
386 ]
387 day_ranges = [7, 15, 30, 60, 90]
388 for days in day_ranges: 388 ↛ 389, 388 ↛ 3922 missed branches: 1) line 388 didn't jump to line 389, because the loop on line 388 never started, 2) line 388 didn't jump to line 392, because the loop on line 388 didn't complete
389 named_ranges.append(('plus_minus_{}d'.format(days), (t_tz - timedelta(days=days), t_tz + timedelta(days=days))))
390 named_ranges.append(('prev_{}d'.format(days), (t_tz - timedelta(days=days), t_tz)))
391 named_ranges.append(('next_{}d'.format(days), (t_tz, t_tz + timedelta(days=days))))
392 for name, res in named_ranges:
393 # print('testing', name)
394 self.assertEqual(get_date_range_by_name(name, t), res)
396 def test_bank_info(self):
397 ac = 'FI8847304720017517'
398 inf = iban_bank_info(ac)
399 self.assertEqual(inf[0], 'POPFFI22')
400 self.assertEqual(inf[1], 'POP-Pankki')
402 ac = ''
403 inf = iban_bank_info(ac)
404 self.assertEqual(inf[0], '')
405 self.assertEqual(inf[1], '')
407 ac= 'BE75270187592710'
408 inf = iban_bank_info(ac)
409 self.assertEqual(inf[0], 'GEBABEBB')
410 self.assertEqual(inf[1], 'BNP Paribas Fortis')
411 ac= 'BE58465045170210'
412 inf = iban_bank_info(ac)
413 self.assertEqual(inf[0], 'KREDBEBB')
414 self.assertEqual(inf[1], 'KBC Bank')
415 ac= 'BE11000123456748'
416 inf = iban_bank_info(ac)
417 self.assertEqual(inf[0], 'BPOTBEB1')
418 self.assertEqual(inf[1], 'bpost bank')
420 def test_org_id_fi(self):
421 valids = [
422 'FI01098230',
423 'FI-01098230',
424 '0109823-0',
425 '2084069-9',
426 ]
427 invalids = [
428 '2084069-1',
429 ]
430 for valid in valids: 430 ↛ 432, 430 ↛ 4342 missed branches: 1) line 430 didn't jump to line 432, because the loop on line 430 never started, 2) line 430 didn't jump to line 434, because the loop on line 430 didn't complete
431 # print('test_org_id_fi:', valid, 'should be valid...', end=' ')
432 fi_company_org_id_validator(valid)
433 # print('ok')
434 for invalid in invalids: 434 ↛ 442line 434 didn't jump to line 442, because the loop on line 434 didn't complete
435 try:
436 # print('test_org_id_fi:', invalid, 'should be invalid', end=' ')
437 fi_company_org_id_validator(invalid)
438 self.assertTrue(False)
439 except ValidationError:
440 # print('ok')
441 pass
442 for n in range(10): 442 ↛ exit, 442 ↛ 4432 missed branches: 1) line 442 didn't return from function 'test_org_id_fi', because the loop on line 442 didn't complete, 2) line 442 didn't jump to line 443, because the loop on line 442 never started
443 v0 = fi_company_org_id_generator()
444 # print(v0)
445 fi_company_org_id_validator(v0)
447 def test_reference_number_validators(self):
448 valid_fi_refs = [
449 '302300',
450 '202196',
451 '302290',
452 ]
453 for ref_no in valid_fi_refs: 453 ↛ 456line 453 didn't jump to line 456
454 fi_payment_reference_validator(ref_no)
456 invalid_fi_refs = [
457 '302301',
458 '202195',
459 '302291',
460 ]
461 for ref_no in invalid_fi_refs: 461 ↛ 462, 461 ↛ 4682 missed branches: 1) line 461 didn't jump to line 462, because the loop on line 461 never started, 2) line 461 didn't jump to line 468
462 try:
463 fi_payment_reference_validator(ref_no)
464 self.assertFalse(True, '{} should have failed validation'.format(ref_no))
465 except ValidationError:
466 pass
468 valid_iso_refs = [
469 'RF92 1229',
470 'RF11 1232',
471 'RF48 1245',
472 ]
473 for ref_no in valid_iso_refs: 473 ↛ exit, 473 ↛ 4742 missed branches: 1) line 473 didn't return from function 'test_reference_number_validators', because the loop on line 473 didn't complete, 2) line 473 didn't jump to line 474, because the loop on line 473 never started
474 iso_payment_reference_validator(ref_no)
476 def test_fi_ssn_age(self):
477 samples = [
478 (date(2018, 12, 20), '231298-965X', 19),
479 (date(2018, 12, 22), '231298-965X', 19),
480 (date(2018, 12, 23), '231298-965X', 20),
481 (date(2018, 12, 24), '231298-965X', 20),
482 ]
483 for date_now, ssn, age in samples: 483 ↛ exitline 483 didn't return from function 'test_fi_ssn_age', because the loop on line 483 didn't complete
484 self.assertEqual(fi_ssn_age(ssn, date_now), age, msg='{} age is {} on {} but fi_ssn_age result was {}'.format(ssn, age, date_now, fi_ssn_age(ssn, date_now)))
486 def test_se_banks(self):
487 self.assertEqual(se_clearing_code_bank_info('6789'), ('Handelsbanken', 9))
488 se_iban_validator('SE45 5000 0000 0583 9825 7466')
489 self.assertEqual(se_clearing_code_bank_info('9500'), ('Nordea AB', 10))
490 an = '957033025420'
491 bank_name, acc_digits = se_clearing_code_bank_info(an)
492 self.assertEqual(bank_name, 'Sparbanken Syd')
493 self.assertGreaterEqual(len(an)-4, acc_digits)
495 def test_dk_banks(self):
496 an = 'DK50 0040 0440 1162 43'
497 dk_iban_validator(an)
498 bic, name = dk_iban_bank_info(an)
499 self.assertEqual(name, 'Nordea')
500 an = '8114 0008874093'
501 name = dk_clearing_code_bank_name(an)
502 self.assertEqual(name, 'Nykredit Bank')
503 an = 'DK2520006893703029'
504 name = dk_clearing_code_bank_name(an)
505 self.assertEqual(name, 'Nordea')
507 def test_ascii_filter(self):
508 pairs = [
509 ('Åke va Källe o Öring', 'Ake va Kalle o Oring'),
510 ('Tôi đang đi mua sắm', 'Toi ang i mua sam'),
511 ('HELÉN FRANZÉN', 'HELEN FRANZEN'),
512 ]
513 for a, b in pairs: 513 ↛ exitline 513 didn't return from function 'test_ascii_filter', because the loop on line 513 didn't complete
514 self.assertEqual(ascii_filter(a), b, 'ascii_filter("{}") != "{}"'.format(b, ascii_filter(a)))
516 def test_l10n(self):
517 from rest_framework.exceptions import ValidationError
519 with override('fi'):
520 msg = _("“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format.")
521 if 'muoto ei kelpaa' not in msg: 521 ↛ 523line 521 didn't jump to line 523, because the condition on line 521 was never false
522 print(msg)
523 self.assertTrue('muoto ei kelpaa' in msg)
525 try:
526 parse_bool('hello')
527 except ValidationError as e:
528 self.assertEqual(str(e), "[ErrorDetail(string='hello ei ole yksi valittavissa olevista arvoista', code='invalid')]")
530 def test_sanitizers(self):
531 self.assertEqual(country_code_sanitizer('kods'), '')
532 self.assertEqual(country_code_sanitizer('fi'), 'FI')
533 self.assertEqual(phone_sanitizer('+13146094459'), '+13146094459')
534 self.assertEqual(phone_sanitizer('13146094459'), '13146094459')
535 self.assertEqual(phone_sanitizer('+13146094459A'), '+13146094459')
536 self.assertEqual(email_sanitizer('test@example.com'), 'test@example.com')
537 self.assertEqual(email_sanitizer('testexample.com'), '')
539 def test_dict_to_html(self):
540 a = {'b': 1, 'c': {'@testVariable': '123'}}
541 res = '<pre>B: 1\nC:\n Test variable: 123\n\n</pre>'
542 self.assertEqual(dict_to_html(a), res)
544 def test_format_xml(self):
545 assert settings.XMLLINT_PATH, 'add e.g. XMLLINT_PATH = "/usr/bin/xmllint" to settings.py'
546 src = '<ApplicationRequest> <CustomerId>1</CustomerId> <Command>DownloadFileList</Command><Timestamp>2019-11-27T04:32:18.613452+02:00</Timestamp><Environment>PRODUCTION</Environment></ApplicationRequest>'
547 dst_ref = '<?xml version="1.0"?>\n<ApplicationRequest>\n <CustomerId>1</CustomerId>\n <Command>DownloadFileList</Command>\n <Timestamp>2019-11-27T04:32:18.613452+02:00</Timestamp>\n <Environment>PRODUCTION</Environment>\n</ApplicationRequest>\n'
548 dst = format_xml(src)
549 self.assertEqual(dst, dst_ref)
550 dst = format_xml_bytes(src.encode())
551 self.assertEqual(dst, dst_ref.encode())
553 def test_parse_sftp(self):
554 test_cases = [
555 ('jani@kajala.com', ['jani', '', 'kajala.com', '']),
556 ('jani.kajala:1231!@kajala.com', ['jani.kajala', '1231!', 'kajala.com', '']),
557 ('jani:1231!@kajala.com:/my/dir', ['jani', '1231!', 'kajala.com', '/my/dir']),
558 ('jani.kajala:1231!@kajala.com:my/dir', ['jani.kajala', '1231!', 'kajala.com', 'my/dir']),
559 ('user=jani;host=kajala.com', ['jani', '', 'kajala.com', '']),
560 ('user=jani.kajala;pass=1231!;host=kajala.com', ['jani.kajala', '1231!', 'kajala.com', '']),
561 ('user=jani;pass=1231!;host=kajala.com;path=/my/dir', ['jani', '1231!', 'kajala.com', '/my/dir']),
562 ('user=jani.kajala;pass=1231!;host=kajala.com;path=my/dir', ['jani.kajala', '1231!', 'kajala.com', 'my/dir']),
563 ]
564 for connection, ref_res in test_cases: 564 ↛ exitline 564 didn't return from function 'test_parse_sftp', because the loop on line 564 didn't complete
565 res = parse_sftp_connection(connection)
566 self.assertListEqual(list(res), ref_res, 'SFTP connection string "{}" parsed incorrectly'.format(connection))
568 def test_admin(self):
569 obj = self.user
570 admin_log([obj], 'Hello, world')
571 admin_log([obj], 'Hello, world', user=self.user, ip='127.0.0.1')
572 admin_log(obj, 'Hello, world', user=self.user, ip='127.0.0.1')
573 e = LogEntry.objects.all().filter(object_id=obj.id).last()
574 self.assertIsNotNone(e)
575 assert isinstance(e, LogEntry)
576 self.assertEqual(e.change_message, 'Hello, world')
577 self.assertEqual(admin_obj_url(obj, 'admin:auth_user_change'), '/admin/auth/user/{}/change/'.format(obj.id))
578 self.assertEqual(admin_obj_url(None, 'admin:auth_user_change'), '')
579 self.assertEqual(admin_obj_link(None, 'admin:auth_user_change'), '')
580 self.assertEqual(admin_obj_url(obj), '/admin/auth/user/{}/change/'.format(obj.id))
581 self.assertEqual(admin_obj_url(e), '/admin/admin/logentry/1/change/'.format(e.id))
582 link = admin_obj_link(obj, 'User', 'admin:auth_user_change')
583 self.assertEqual(link, "<a href='/admin/auth/user/{}/change/'>User</a>".format(obj.id))
585 def test_cmd_parser(self):
586 parser = CommandParser()
587 add_date_range_arguments(parser)
588 argv = parser.parse_args(['--begin', '2019-06-25', '--end', '2020-02-01'])
589 options = argv.__dict__
590 begin, end, steps = parse_date_range_arguments(options)
591 self.assertEqual(begin, pytz.utc.localize(datetime(2019, 6, 25)))
592 self.assertEqual(end, pytz.utc.localize(datetime(2020, 2, 1)))
594 def test_format_timedelta(self):
595 self.assertEqual(format_timedelta(timedelta(seconds=90)), '1min30s')
596 self.assertEqual(format_timedelta(timedelta(seconds=3600+90)), '1h1min30s')
597 self.assertEqual(format_timedelta(timedelta(seconds=90), minutes_label='min ', seconds_label='s '), '1min 30s')
598 self.assertEqual(format_timedelta(timedelta(seconds=3600+90), hours_label='h ', minutes_label='min ', seconds_label='s '), '1h 1min 30s')
599 self.assertEqual(format_timedelta(timedelta(seconds=90), seconds_label=''), '1min')
601 def test_dec123456(self):
602 self.assertEqual(dec1(Decimal('1.2345678')), Decimal('1.2'))
603 self.assertEqual(dec2(Decimal('1.2345678')), Decimal('1.23'))
604 self.assertEqual(dec3(Decimal('1.2345678')), Decimal('1.235'))
605 self.assertEqual(dec4(Decimal('1.2345678')), Decimal('1.2346'))
606 self.assertEqual(dec5(Decimal('1.2345678')), Decimal('1.23457'))
607 self.assertEqual(dec6(Decimal('1.2345678')), Decimal('1.234568'))
609 def test_model_funcs(self):
610 admin_log([self.user], 'test msg 1')
611 obj = LogEntry.objects.all().order_by('-pk').last()
612 assert isinstance(obj, LogEntry)
613 self.assertFalse(is_model_field_changed(obj, 'change_message'))
614 obj.change_message = 'hello world'
615 self.assertTrue(is_model_field_changed(obj, 'change_message'))
616 obj.save()
617 self.assertFalse(is_model_field_changed(obj, 'change_message'))
618 obj2 = clone_model(obj)
619 assert isinstance(obj2, LogEntry)
620 self.assertEqual(obj.change_message, obj2.change_message)
621 self.assertGreater(obj2.pk, obj.pk)
622 label, val = get_model_field_label_and_value(obj, 'action_time')
623 self.assertEqual(label, _('action time'))
624 self.assertEqual(str(obj.action_time), val)
625 obj_b = get_object_or_none(obj.__class__, id=obj.id)
626 self.assertEqual(obj_b.id, obj.id)
627 obj_b = get_object_or_none(obj.__class__, id=-1)
628 self.assertIsNone(obj_b)
630 def test_format_table(self):
631 a = [
632 ['date', 'description', 'count', 'unit price', 'total price'],
633 [date(2019, 12, 15), 'oranges', 1000, dec2('0.99'), dec2('990.00')],
634 [date(2020, 1, 3), 'apples', 4, dec2('1.10'), dec2('4.40')],
635 [date(2020, 11, 3), 'apples', 5, dec2('10.10'), dec2('50.50')],
636 ]
637 out = format_table(a, has_label_row=True, max_col=10)
638 out_ref = """
639---------------------------------------------------
640| date|descript..|count|unit price|total pr..|
641---------------------------------------------------
642|2019-12-15| oranges| 1000| 0.99| 990.00|
643|2020-01-03| apples| 4| 1.10| 4.40|
644|2020-11-03| apples| 5| 10.10| 50.50|
645---------------------------------------------------
646 """.strip()
647 self.assertEqual(out, out_ref)
649 out = format_table(a, has_label_row=True, max_col=20)
650 out_ref = """
651-----------------------------------------------------
652| date|description|count|unit price|total price|
653-----------------------------------------------------
654|2019-12-15| oranges| 1000| 0.99| 990.00|
655|2020-01-03| apples| 4| 1.10| 4.40|
656|2020-11-03| apples| 5| 10.10| 50.50|
657-----------------------------------------------------
658 """.strip()
659 self.assertEqual(out, out_ref)
661 out = format_table(a, has_label_row=True, max_col=20, col_sep=' | ')
662 out_ref = """
663-------------------------------------------------------------
664| date | description | count | unit price | total price|
665-------------------------------------------------------------
666|2019-12-15 | oranges | 1000 | 0.99 | 990.00|
667|2020-01-03 | apples | 4 | 1.10 | 4.40|
668|2020-11-03 | apples | 5 | 10.10 | 50.50|
669-------------------------------------------------------------
670 """.strip()
671 self.assertEqual(out, out_ref)
673 out = format_table(a, has_label_row=True, max_col=20, col_sep=' | ', left_align=[1])
674 out_ref = """
675-------------------------------------------------------------
676| date | description | count | unit price | total price|
677-------------------------------------------------------------
678|2019-12-15 | oranges | 1000 | 0.99 | 990.00|
679|2020-01-03 | apples | 4 | 1.10 | 4.40|
680|2020-11-03 | apples | 5 | 10.10 | 50.50|
681-------------------------------------------------------------
682 """.strip()
683 self.assertEqual(out, out_ref)
685 out = format_table(a, has_label_row=True, max_col=20, col_sep=' | ', left_align=[1], max_line=50)
686 out_ref = """
687-------------------------------------------------
688| date | description | count | unit price|..
689-------------------------------------------------
690|2019-12-15 | oranges | 1000 | 0.99|..
691|2020-01-03 | apples | 4 | 1.10|..
692|2020-11-03 | apples | 5 | 10.10|..
693-------------------------------------------------
694 """.strip()
695 self.assertEqual(out, out_ref)
697 out = format_table(a, left_align=[1], center_align=[0,2,3,4], max_col=50)
698 out_ref = """
699-----------------------------------------------------
700| date |description|count|unit price|total price|
701|2019-12-15|oranges |1000 | 0.99 | 990.00 |
702|2020-01-03|apples | 4 | 1.10 | 4.40 |
703|2020-11-03|apples | 5 | 10.10 | 50.50 |
704-----------------------------------------------------
705 """.strip()
706 self.assertEqual(out, out_ref)
708 def test_ucfirst_lazy(self):
709 s = gettext_lazy(ucfirst_lazy("missing value"))
710 s_ref = gettext_lazy("Missing value")
711 s_en = "Missing value"
712 s_fi = "Puuttuva arvo"
713 with override('fi'):
714 self.assertEqual(s, s_fi)
715 self.assertEqual(s, s_ref)
716 with override('en'):
717 self.assertEqual(s, s_en)
718 self.assertEqual(s, s_ref)
720 def test_media_paths(self):
721 media_root1 = os.path.join(settings.MEDIA_ROOT, 'path1/path2')
722 media_root2 = os.path.join(settings.MEDIA_ROOT, 'path3') + '/'
723 test_paths = [
724 (os.path.join(media_root1, 'test1.file'), 'path1/path2/test1.file'),
725 (os.path.join('/diff/path', 'test1.file'), '/diff/path/test1.file'),
726 (os.path.join(media_root2, 'test1.file'), 'path3/test1.file'),
727 ]
728 for src, dst in test_paths: 728 ↛ exit, 728 ↛ 7292 missed branches: 1) line 728 didn't return from function 'test_media_paths', because the loop on line 728 didn't complete, 2) line 728 didn't jump to line 729, because the loop on line 728 never started
729 self.assertEqual(strip_media_root(src), dst)
730 self.assertEqual(get_media_full_path(dst), src)
732 def test_end_of_month(self):
733 helsinki = pytz.timezone('Europe/Helsinki')
734 # 1
735 time_now = datetime(2020, 6, 5, 15, 47, 23, 818646)
736 eom = end_of_month(time_now, tz=helsinki)
737 eom_ref = helsinki.localize(datetime(2020, 6, 30, 23, 59, 59, 999999))
738 self.assertEqual(eom, eom_ref)
739 # 2
740 time_now = datetime(2020, 7, 5, 15, 47, 23, 818646)
741 eom = end_of_month(time_now, tz=helsinki)
742 eom_ref = helsinki.localize(datetime(2020, 7, 31, 23, 59, 59, 999999))
743 self.assertEqual(eom, eom_ref)
744 # 3
745 time_now = datetime(2020, 6, 5, 15, 47, 23, 818646)
746 eom = end_of_month(time_now, n=1, tz=helsinki)
747 eom_ref = helsinki.localize(datetime(2020, 7, 31, 23, 59, 59, 999999))
748 self.assertEqual(eom, eom_ref)
749 # 4
750 time_now = datetime(2020, 7, 5, 15, 47, 23, 818646)
751 eom = end_of_month(time_now, n=-2, tz=helsinki)
752 eom_ref = helsinki.localize(datetime(2020, 5, 31, 23, 59, 59, 999999))
753 self.assertEqual(eom, eom_ref)
755 def test_iban_generator_and_validator(self):
756 test_ibans = [
757 'MD7289912714638112731113',
758 'IS363252851674877586492113',
759 'HR5125000099152386224',
760 'CZ4750515755735423825528',
761 'FI3253811381259333',
762 'FR3212739000501869481882E94',
763 ]
764 for iban in test_ibans:
765 iban_validator(iban)
766 for n in range(100):
767 acc = iban_generator()
768 # print(acc)
769 try:
770 iban_validator(acc)
771 except Exception as e:
772 print('iban_generator() returned', acc, 'but iban_validator() raised exception', e)
773 self.fail('iban_validator(iban_generator()) should not raise Exception, account number was {}'.format(acc))
775 def test_make_email_recipient(self):
776 email_tests = [
777 {
778 'list': [
779 ('Jani Kajala', 'kajala@example.com'),
780 '"Jani Kajala" <kajala@example.com>',
781 '<kajala@example.com>',
782 'kajala@example.com',
783 ],
784 'result': [
785 ('Jani Kajala', 'kajala@example.com'),
786 ('Jani Kajala', 'kajala@example.com'),
787 ('kajala@example.com', 'kajala@example.com'),
788 ('kajala@example.com', 'kajala@example.com'),
789 ]
790 }
791 ]
792 for et in email_tests:
793 res = make_email_recipient_list(et['list'])
794 self.assertListEqual(res, et['result'])
796 def test_choices(self):
797 val = choices_label(MY_CHOICES, MY_CHOICE_1)
798 self.assertEqual(val, 'MY_CHOICE_1')
800 def test_camel_case(self):
801 pairs = [
802 ('camelCaseWord', 'camel_case_word'),
803 ('camelCase', 'camel_case'),
804 ('camel', 'camel'),
805 ('camelCCase', 'camel_c_case'),
806 ]
807 for cc, us in pairs:
808 self.assertEqual(camel_case_to_underscore(cc), us)
809 self.assertEqual(cc, underscore_to_camel_case(us))
811 def create_dummy_request(self, path: str = '/admin/login/'):
812 request = request_factory.get('/admin/login/')
813 request.user = self.user # type: ignore
814 return request
816 def test_model_admin_base(self):
817 # test that actions sorting by name works
818 request = self.create_dummy_request()
819 user = self.user
820 model_admin = MyCustomAdmin(LogEntry, admin.site)
821 res = model_admin.get_actions(request)
822 self.assertEqual(list(res.items())[0][0], 'dummy_admin_func_a', 'ModelAdminBase.get_actions sorting failed')
823 self.assertEqual(list(res.items())[1][0], 'dummy_admin_func_b', 'ModelAdminBase.get_actions sorting failed')
825 # create 10 LogEntry for test user, 5 with text "VisibleLogMessage" and 5 "InvisibleLogMessage"
826 # then check that "VisibleLogMessage" log entries are not visible since max_history_length = 5
827 LogEntry.objects.filter(object_id=user.id).delete()
828 for n in range(5):
829 admin_log([user], 'VisibleLogMessage')
830 for n in range(5):
831 admin_log([user], 'InvisibleLogMessage')
832 self.assertEqual(LogEntry.objects.filter(object_id=user.id).count(), 10)
833 history_url = '/admin/auth/user/{}/history/'.format(user.id)
834 c = self.client
835 c.get(history_url, follow=True)
836 c.post('/admin/login/', {'username': 'test@example.com', 'password': 'test1234'})
837 res = c.get(history_url)
838 content = res.content.decode()
839 assert isinstance(content, str)
840 self.assertEqual(content.count('VisibleLogMessage'), 5)
841 self.assertEqual(content.count('InvisibleLogMessage'), 0)
843 def test_admin_log_entry_mixin(self):
844 user = self.user
845 AdminLogEntryMixin.fields_changed(user, ['username'], who=None)
846 e = LogEntry.objects.filter(object_id=user.id).last()
847 assert isinstance(e, LogEntry)
848 self.assertEqual(e.change_message, 'User id={}: username=test@example.com'.format(user.id))
850 def test_admin_file_download_mixin(self):
851 class MyModel(models.Model):
852 file = models.FileField(upload_to='uploads')
853 model_admin = MyCustomAdmin(MyModel, admin.site)
854 self.assertListEqual(model_admin.get_file_fields(), ['file'])
855 self.assertEqual(model_admin.single_file_field, 'file')
856 self.assertEqual(len(model_admin.get_download_urls()), 2)
857 res = model_admin.file_download_view(self.create_dummy_request(), 'requirements.txt')
858 self.assertTrue(isinstance(res, FileSystemFileResponse))
860 def test_auth(self):
861 req = self.create_dummy_request()
862 require_auth(req) # type: ignore
863 req.user = None
864 self.assertIsNone(require_auth(req, exceptions=False))
865 try:
866 require_auth(req) # type: ignore
867 self.fail('require_auth fail')
868 except NotAuthenticated:
869 pass
870 try:
871 model_admin = AuthUserMixin()
872 model_admin.request = req
873 user = model_admin.auth_user
874 self.fail('require_auth fail')
875 except NotAuthenticated:
876 pass
879admin.site.unregister(User)
880admin.site.register(User, MyCustomAdmin)
881admin.site.register(LogEntry, MyCustomAdmin)