Hide keyboard shortcuts

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 

6 

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 

18 

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 

49 

50 

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) 

57 

58request_factory = RequestFactory() 

59 

60 

61def dummy_admin_func_a(modeladmin, request, qs): 

62 print('dummy_admin_func_a') 

63dummy_admin_func_a.short_description = 'A' # type: ignore 

64 

65 

66def dummy_admin_func_b(modeladmin, request, qs): 

67 print('dummy_admin_func_b') 

68dummy_admin_func_b.short_description = 'B' # type: ignore 

69 

70 

71class MyCustomAdmin(ModelAdminBase, AdminFileDownloadMixin): 

72 max_history_length = 5 

73 actions = ( 

74 dummy_admin_func_b, 

75 dummy_admin_func_a, 

76 ) 

77 

78 def get_object(self, request, obj_id): 

79 return self.model.objects.get(id=obj_id) 

80 

81 def get_object_by_filename(self, request, filename): 

82 return User.objects.first() # dummy return for test_admin_file_download_mixin 

83 

84 

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() 

94 

95 def tearDown(self): 

96 super().setUp() 

97 

98 def test_payment_reference(self): 

99 self.assertEqual(fi_payment_reference_number('100'), '1009') 

100 

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 

119 

120 

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') 

136 

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') 

142 

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'), '') 

151 

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'), '') 

157 

158 def test_country_code(self): 

159 with self.assertRaisesMessage(ValidationError, _('Invalid country code')): 

160 country_code_validator('Finland') 

161 

162 def test_bic(self): 

163 bic = iban_bic('FI21 1234 5600 0007 85') 

164 self.assertEqual(bic, 'NDEAFIHH') 

165 

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') 

173 

174 def test_validate_country_iban(self): 

175 with self.assertRaisesMessage(ValidationError, _('Invalid IBAN account number')): 

176 validate_country_iban('FI15616515616156', 'SE') 

177 

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)) 

187 

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') 

207 

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')) 

213 

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)) 

236 

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') 

244 

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') 

255 

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) 

265 

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) 

272 

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) 

281 

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) 

302 

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) 

323 

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) 

346 

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) 

356 

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) 

363 

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))) 

375 

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) 

395 

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') 

401 

402 ac = '' 

403 inf = iban_bank_info(ac) 

404 self.assertEqual(inf[0], '') 

405 self.assertEqual(inf[1], '') 

406 

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') 

419 

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) 

446 

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) 

455 

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 

467 

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) 

475 

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))) 

485 

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) 

494 

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') 

506 

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))) 

515 

516 def test_l10n(self): 

517 from rest_framework.exceptions import ValidationError 

518 

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) 

524 

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')]") 

529 

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'), '') 

538 

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) 

543 

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()) 

552 

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)) 

567 

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)) 

584 

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))) 

593 

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') 

600 

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')) 

608 

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) 

629 

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) 

648 

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) 

660 

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) 

672 

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) 

684 

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) 

696 

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) 

707 

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) 

719 

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) 

731 

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) 

754 

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)) 

774 

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']) 

795 

796 def test_choices(self): 

797 val = choices_label(MY_CHOICES, MY_CHOICE_1) 

798 self.assertEqual(val, 'MY_CHOICE_1') 

799 

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)) 

810 

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 

815 

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') 

824 

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) 

842 

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)) 

849 

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)) 

859 

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 

877 

878 

879admin.site.unregister(User) 

880admin.site.register(User, MyCustomAdmin) 

881admin.site.register(LogEntry, MyCustomAdmin)