Coverage for jutil/tests.py: 61%

# Coverage for jutil/tests.py : 61%

## 562 statements 361 run 201 missing 0 excluded 25 partial

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

1 import os

2 from datetime import datetime, timedelta, date

3 from decimal import Decimal

4 from os.path import join

5 from pprint import pprint

6

7 import pytz

8 from django.conf import settings

9 from django.contrib.admin.models import LogEntry

10 from django.contrib.auth.models import User

11 from django.core.exceptions import ValidationError

12 from django.core.management.base import CommandParser # type: ignore

13 from django.db import models

14 from django.test import TestCase

15 from django.test.client import RequestFactory, Client

16 from django.utils.translation import override, gettext as _, gettext_lazy

17 from rest_framework.exceptions import NotAuthenticated

18

19 from jutil.admin import admin_log, admin_obj_url, admin_obj_link, ModelAdminBase, AdminLogEntryMixin, \

20 AdminFileDownloadMixin

21 from jutil.auth import require_auth, AuthUserMixin

22 from jutil.command import get_date_range_by_name, add_date_range_arguments, parse_date_range_arguments

23 from jutil.dict import dict_to_html, choices_label

24 from jutil.email import make_email_recipient_list

25 from jutil.model import is_model_field_changed, clone_model, get_model_field_label_and_value, get_object_or_none

26 from jutil.request import get_ip_info

27 from jutil.responses import FileSystemFileResponse

28 from jutil.sftp import parse_sftp_connection

29 from jutil.testing import DefaultTestSetupMixin

30 from jutil.urls import url_equals, url_mod, url_host

31 from jutil.xml import xml_to_dict, dict_to_element, _xml_filter_tag_name

32 from 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

34 from 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

37 from jutil.parse import parse_datetime, parse_bool

38 from 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

46 from xml.etree.ElementTree import Element

47 from xml.etree import ElementTree as ET

48 from django.contrib import admin

49

50

51 MY_CHOICE_1 = '1'

52 MY_CHOICE_2 = '2'

53 MY_CHOICES = (

54 (MY_CHOICE_1, 'MY_CHOICE_1'),

55 (MY_CHOICE_2, 'MY_CHOICE_2'),

56)

57

58 request_factory = RequestFactory()

59

60

61 def dummy_admin_func_a(modeladmin, request, qs):

62 print('dummy_admin_func_a')

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

64

65

66 def dummy_admin_func_b(modeladmin, request, qs):

67 print('dummy_admin_func_b')

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

69

70

71 class 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

85 class 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

879 admin.site.unregister(User)

880 admin.site.register(User, MyCustomAdmin)

881 admin.site.register(LogEntry, MyCustomAdmin)