Coverage for jutil/tests.py: 97%

# Coverage for jutil/tests.py : 97%

## 697 statements

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 logging

2 import os

3 from datetime import datetime, timedelta, date

4 from decimal import Decimal

5 from os.path import join

6 from pprint import pprint

7 from urllib.parse import urlparse

8 from django.utils import timezone

9 from typing import List

10

11 from django.utils.timezone import now

12

13 from jutil.modelfields import SafeCharField, SafeTextField

14 from jutil.middleware import logger as jutil_middleware_logger, ActivateUserProfileTimezoneMiddleware

15 import pytz

16 from django.conf import settings

17 from django.contrib.admin.models import LogEntry

18 from django.contrib.auth.models import User

19 from django.core.exceptions import ValidationError

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

21 from django.db import models

22 from django.http.response import HttpResponse

23 from django.test import TestCase

24 from django.test.client import RequestFactory, Client

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

26 from rest_framework.exceptions import NotAuthenticated

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

28 AdminFileDownloadMixin

29 from jutil.auth import require_auth, AuthUserMixin

30 from jutil.command import get_date_range_by_name, add_date_range_arguments, parse_date_range_arguments

31 from jutil.dict import dict_to_html, choices_label

32 from jutil.email import make_email_recipient_list

33 from jutil.middleware import EnsureOriginMiddleware, LogExceptionMiddleware, EnsureLanguageCookieMiddleware

34 from jutil.model import is_model_field_changed, clone_model, get_model_field_label_and_value, get_object_or_none, \

35 wait_object_or_none

36 from jutil.request import get_ip_info

37 from jutil.responses import FileSystemFileResponse, CsvResponse

38 from jutil.testing import DefaultTestSetupMixin

39 from jutil.urls import url_equals, url_mod, url_host

40 from jutil.xml import xml_to_dict, dict_to_element, _xml_filter_tag_name

41 from jutil.dates import add_month, per_delta, per_month, this_week, next_month, next_week, this_month, last_month, \

42 last_year, last_week, yesterday, end_of_month

43 from jutil.format import format_full_name, format_xml, format_xml_bytes, format_timedelta, dec1, dec2, dec3, dec4, dec5, \

44 dec6, format_table, ucfirst_lazy, strip_media_root, get_media_full_path, camel_case_to_underscore, \

45 underscore_to_camel_case

46 from jutil.parse import parse_datetime, parse_bool

47 from jutil.validators import fi_payment_reference_number, se_ssn_validator, se_ssn_filter, fi_iban_validator, \

48 se_iban_validator, iban_filter_readable, email_filter, iban_validator, iban_bank_info, fi_company_org_id_validator, \

49 email_validator, fi_payment_reference_validator, iso_payment_reference_validator, fi_ssn_age, \

50 se_clearing_code_bank_info, ascii_filter, ee_iban_validator, be_iban_validator, dk_iban_validator, \

51 dk_iban_bank_info, dk_clearing_code_bank_name, country_code_sanitizer, phone_sanitizer, email_sanitizer, \

52 fi_company_org_id_generator, phone_validator, passport_filter, passport_validator, passport_sanitizer, \

53 country_code_validator, validate_country_iban, iban_bic, validate_country_company_org_id, fi_ssn_generator, \

54 fi_ssn_validator, bic_validator, iban_generator, bic_sanitizer, filter_country_company_org_id

55 from xml.etree.ElementTree import Element

56 from xml.etree import ElementTree as ET

57 from django.contrib import admin

58

59

60 MY_CHOICE_1 = '1'

61 MY_CHOICE_2 = '2'

62 MY_CHOICES = (

63 (MY_CHOICE_1, 'MY_CHOICE_1'),

64 (MY_CHOICE_2, 'MY_CHOICE_2'),

65)

66

67 request_factory = RequestFactory()

68

69

70 class DummyLogHandler(logging.Handler):

71 msgs: List[str]

72

73 def __init__(self):

74 super().__init__()

75 self.msgs = []

76

77 def emit(self, record):

78 msg = self.format(record)

79 self.msgs.append(msg)

80

81

82 def dummy_time_zone_response(obj) -> HttpResponse:

83 tz = timezone.get_current_timezone()

84 return HttpResponse(str(tz).encode())

85

86

87 def dummy_admin_func_a(modeladmin, request, qs):

88 print('dummy_admin_func_a')

89

90

91 def dummy_admin_func_b(modeladmin, request, qs):

92 print('dummy_admin_func_b')

93

94

95 def dummy_middleware_get_response(obj) -> HttpResponse:

96 return HttpResponse(b'hello content')

97

98

99 class DummyUserProfile:

100 timezone = 'Europe/Helsinki'

101

102

103 class MyCustomAdmin(ModelAdminBase, AdminFileDownloadMixin):

104 max_history_length = 5

105 actions = (

106 dummy_admin_func_b,

107 dummy_admin_func_a,

108 )

109

110 def get_object(self, request, obj_id):

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

112

113 def get_object_by_filename(self, request, filename):

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

115

116

117 class Tests(TestCase, DefaultTestSetupMixin):

118 def setUp(self):

119 super().setUp()

120 user = self.add_test_user('test@example.com', 'test1234')

121 assert isinstance(user, User)

122 user.is_superuser = True

123 user.is_staff = True

124 user.save()

125 self.client = Client()

126

127 def tearDown(self):

128 super().setUp()

129

130 def test_payment_reference(self):

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

132

133 def test_format_full_name(self):

134 samples = [

135 ('Short', 'Full Name', 'Short Full Name'),

136 ('Short Middle Name Is Quite Long', 'Full Name', 'Short Full Name'),

137 ('Short-Middle Name Is Quite Long', 'Full Name', 'Short Full Name'),

138 ('Olga Demi', 'Serpuhovitinova-Miettinen', 'Olga Serpuhovitinova'),

139 ('Olga-Anne Demi', 'Serpuhovitinovatsko', 'Olga S'),

140 ]

141 for v in samples:

142 limited = format_full_name(v[0], v[1], 20)

143 # print('{} {} -> {} (was: {})'.format(v[0], v[1], v[2], limited))

144 self.assertEqual(v[2], limited)

145 try:

146 long_name = '19280309812083091829038190823081208301280381092830182038018203810283021'

147 format_full_name(long_name, long_name)

148 self.fail('format_full_name failed with long name')

149 except Exception:

150 pass

151

152 def test_add_month(self):

153 t = parse_datetime('2016-06-12T01:00:00')

154 self.assertTrue(isinstance(t, datetime))

155 assert isinstance(t, datetime)

156 self.assertEqual(t.isoformat(), '2016-06-12T01:00:00+00:00')

157 t2 = add_month(t)

158 self.assertEqual(t2.isoformat(), '2016-07-12T01:00:00+00:00')

159 t3 = add_month(t, 7)

160 self.assertEqual(t3.isoformat(), '2017-01-12T01:00:00+00:00')

161 t4 = add_month(t, -1)

162 self.assertEqual(t4.isoformat(), '2016-05-12T01:00:00+00:00')

163 time_now = datetime(2020, 6, 30, 15, 47, 23, 818646)

164 self.assertEqual(add_month(time_now, -4).isoformat(), '2020-02-29T15:47:23.818646')

165 self.assertEqual(add_month(time_now, 8).isoformat(), '2021-02-28T15:47:23.818646')

166 self.assertEqual(add_month(time_now, 0).isoformat(), '2020-06-30T15:47:23.818646')

167

168 def test_se_ssn(self):

169 se_ssn_validator('811228-9874')

170 se_ssn_validator('670919-9530')

171 with self.assertRaises(ValidationError):

172 se_ssn_validator('811228-9873')

173

174 def test_phone_numbers(self):

175 try:

176 phone_validator('+358456343767')

177 except Exception:

178 self.fail('phone_validator("+358456343767") should not raise Exception')

179 with self.assertRaisesMessage(ValidationError, _('Invalid phone number')):

180 phone_validator('214')

181 self.assertEqual(phone_sanitizer('214'), '')

182

183 def test_passport(self):

184 self.assertEqual(passport_filter('?ADsd-12312dsds'), 'ADSD-12312DSDS')

185 passport_validator('21412312312')

186 with self.assertRaisesMessage(ValidationError, _('Invalid passport number')):

187 passport_validator('214')

188 self.assertEqual(passport_sanitizer('214'), '')

189 self.assertEqual(passport_sanitizer('21412312312'), '21412312312')

190

191 def test_country_code(self):

192 for cc in ['FI', 'DK', 'ES', 'SE', 'VN']:

193 country_code_validator(cc)

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

195 country_code_validator('Finland')

196

197 def test_bic(self):

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

199 self.assertEqual(bic, 'NDEAFIHH')

200 self.assertEqual(bic_sanitizer('NDEAFIHH'), 'NDEAFIHH')

201 self.assertEqual(bic_sanitizer('NDEAFIH'), '')

202

203 def test_org_id(self):

204 try:

205 validate_country_company_org_id('FI', '2084069-9')

206 except Exception:

207 self.fail('2084069-9 is valid org id')

208 with self.assertRaisesMessage(ValidationError, _('Invalid company organization ID')):

209 validate_country_company_org_id('FI', '2084069-8')

210

211 def test_validate_country_iban(self):

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

213 validate_country_iban('FI15616515616156', 'SE')

214

215 def test_fi_ssn_generator(self):

216 self.assertEqual(len(fi_ssn_generator()), 6 + 1 + 4)

217 for min_year, max_year in [(1800, 1900), (1900, 2000), (2000, 2050)]:

218 for n in range(10):

219 ssn = fi_ssn_generator(min_year, max_year)

220 try:

221 fi_ssn_age(ssn)

222 fi_ssn_validator(ssn)

223 except Exception:

224 self.fail('{} is valid SSN'.format(ssn))

225 fi_ssn_validator('110305+283X')

226 for ssn in ['9999-123F', '271138-670X', '090228+256X']:

227 with self.assertRaisesMessage(ValidationError, _('Invalid personal identification number')):

228 fi_ssn_validator(ssn)

229 with self.assertRaises(ValidationError):

230 fi_ssn_generator(1700, 1799)

231 with self.assertRaises(ValidationError):

232 fi_ssn_generator(2100, 2200)

233

234 def test_iban(self):

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

236 iban_validator('')

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

238 iban_validator('XX')

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

240 iban_validator('FI2112345600000786')

241 bic_validator('HELSFIHH')

242 bic_validator('HELSFIHHXXX')

243 with self.assertRaisesMessage(ValidationError, _('Invalid bank BIC/SWIFT code')):

244 bic_validator('HELSFIH')

245 with self.assertRaisesMessage(ValidationError, _('Invalid bank BIC/SWIFT code')):

246 bic_validator('')

247 with self.assertRaisesMessage(ValidationError, _('Invalid bank BIC/SWIFT code')):

248 bic_validator('XX123123123112')

249 iban_validator('FI2112345600000785')

250 iban_validator('SE4550000000058398257466')

251 fi_iban_validator('FI2112345600000785')

252 se_iban_validator('SE4550000000058398257466')

253 ee_iban_validator('EE38 2200 2210 2014 5685')

254 be_iban_validator('BE68 5390 0754 7034')

255 with self.assertRaises(ValidationError):

256 fi_iban_validator('FI2112345600000784')

257 with self.assertRaises(ValidationError):

258 se_iban_validator('SE4550000000058398257465')

259 iban = 'FI8847304720017517'

260 self.assertEqual(iban_filter_readable(iban), 'FI88 4730 4720 0175 17')

261 self.assertEqual(iban_filter_readable(''), '')

262

263 def test_urls(self):

264 url = 'http://yle.fi/uutiset/3-8045550?a=123&b=456'

265 self.assertEqual(url_host(url), 'yle.fi')

266 self.assertTrue(

267 url_equals('http://yle.fi/uutiset/3-8045550?a=123&b=456', 'http://yle.fi/uutiset/3-8045550?b=456&a=123'))

268 self.assertTrue(url_equals(url_mod('http://yle.fi/uutiset/3-8045550?a=123&b=456', {'b': '123', 'a': '456'}),

269 'http://yle.fi/uutiset/3-8045550?b=123&a=456'))

270

271 def test_email_filter_and_validation(self):

272 emails = [

273 (' Asdsa@a-a.com ', 'asdsa@a-a.com', True),

274 ('1asdsa@a-a2.com', '1asdsa@a-a2.com', True),

275 (' Asdsa@a-a ', 'asdsa@a-a', False),

276 (' @a-a2.com', '@a-a2.com', False),

277 (' a-a2.com', 'a-a2.com', False),

278 ('ää-a2@ää-a2.com', 'ää-a2@ää-a2.com', False),

279 ('aaa.bbbbb@ccc-ddd.fi', 'aaa.bbbbb@ccc-ddd.fi', True),

280 ]

281 for i, o, is_valid in emails:

282 # print('email_filter({}) -> {}'.format(i, email_filter(i)))

283 self.assertEqual(email_filter(i), o)

284 if is_valid:

285 email_validator(o)

286 else:

287 fail = False

288 try:

289 email_validator(o)

290 except ValidationError:

291 fail = True

292 self.assertTrue(fail, '{} is not valid email but passed validation'.format(o))

293

294 def test_ip_info(self):

295 ip, cc, host = get_ip_info('213.214.146.142')

296 self.assertEqual(ip, '213.214.146.142')

297 if cc: 297 ↛ 299line 297 didn't jump to line 299, because the condition on line 297 was never false

298 self.assertEqual(cc, 'FI')

299 if host: 299 ↛ exitline 299 didn't return from function 'test_ip_info', because the condition on line 299 was never false

300 self.assertEqual(host, '213214146142.edelkey.net')

301

302 def test_parse_xml(self):

303 # finvoice_201_example1.xml

304 xml_bytes = open(join(settings.BASE_DIR, 'data/fi/finvoice_201_example1.xml'), 'rb').read()

305 data = xml_to_dict(xml_bytes, value_key='value', attribute_prefix='_')

306 # pprint(data)

307 self.assertEqual(data['_Version'], '2.01')

308 self.assertEqual(data['InvoiceRow'][0]['ArticleIdentifier'], '12345')

309 self.assertEqual(data['InvoiceRow'][0]['DeliveredQuantity']['value'], '2')

310 self.assertEqual(data['InvoiceRow'][0]['DeliveredQuantity']['_QuantityUnitCode'], 'kpl')

311 self.assertEqual(data['InvoiceRow'][1]['ArticleIdentifier'], '123456')

312

313 # parse_xml1.xml

314 xml_str = open(join(settings.BASE_DIR, 'data/parse_xml1.xml'), 'rt').read()

315 data = xml_to_dict(xml_str.encode())

316 # pprint(data)

317 ref_data = {'@version': '1.2',

318 'A': [{'@class': 'x', 'B': {'@': 'hello', '@class': 'x2'}},

319 {'@class': 'y', 'B': {'@': 'world', '@class': 'y2'}}],

320 'C': 'value node'}

321 self.assertEqual(ref_data, data)

322

323 # parse_xml1.xml / no attributes

324 xml_str = open(join(settings.BASE_DIR, 'data/parse_xml1.xml'), 'rt').read()

325 data = xml_to_dict(xml_str.encode(), parse_attributes=False)

326 # pprint(data)

327 ref_data = {'A': [{'B': 'hello'}, {'B': 'world'}], 'C': 'value node'}

328 self.assertEqual(ref_data, data)

329

330 # parse_xml2.xml / no attributes

331 xml_str = open(join(settings.BASE_DIR, 'data/parse_xml2.xml'), 'rt').read()

332 data = xml_to_dict(xml_str.encode(), ['VastausLoki', 'LuottoTietoMerkinnat'], parse_attributes=False)

333 # pprint(data)

334 ref_data = {'VastausLoki': {'KysyttyHenkiloTunnus': '020685-1234',

335 'PaluuKoodi': 'Palveluvastaus onnistui',

336 'SyyKoodi': '1'}}

337 self.assertEqual(ref_data, data)

338

339 def test_dict_to_xml(self):

340 data = {

341 'Doc': {

342 '@version': '1.2',

343 'A': [{'@class': 'x', 'B': {'@': 'hello', '@class': 'x2'}},

344 {'@class': 'y', 'B': {'@': 'world', '@class': 'y2'}}],

345 'C': 'value node',

346 'D': 123,

347 'E': ['abc'],

348 }

349 }

350 el = dict_to_element(data)

351 assert isinstance(el, Element)

352 xml_str = ET.tostring(el, encoding='utf8', method='xml').decode()

353 # 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>

354 data2 = xml_to_dict(xml_str.encode(), document_tag=True, array_tags=['E'], int_tags=['D'])

355 # print('')

356 # pprint(data)

357 # pprint(data2)

358 self.assertEqual(data2, data)

359

360 def test_dict_to_xml2(self):

361 self.assertEqual(_xml_filter_tag_name('TagName[0]'), 'TagName')

362 self.assertEqual(_xml_filter_tag_name('TagName[1]'), 'TagName')

363 self.assertEqual(_xml_filter_tag_name('TagName'), 'TagName')

364 data = {

365 'Doc': {

366 '@version': '1.2',

367 'A': [{'@class': 'x', 'B': {'@': 'hello', '@class': 'x2'}},

368 {'@class': 'y', 'B': {'@': 'world', '@class': 'y2'}}],

369 'C': 'value node',

370 'D': 123,

371 'E': ['abc'],

372 'F': ['line 1', 'line 2']

373 }

374 }

375 el = dict_to_element(data)

376 assert isinstance(el, Element)

377 xml_str = ET.tostring(el, encoding='utf8', method='xml').decode()

378 data2 = xml_to_dict(xml_str.encode(), document_tag=True, array_tags=['E', 'F'], int_tags=['D'])

379 self.assertEqual(data2, data)

380

381 def test_xml_to_dict(self):

382 xml_str = """<?xml version="1.0" encoding="utf-8"?>

383<Document>

384 <TxsSummry>

385 <TtlNtries>

386 <NbOfNtries>12</NbOfNtries>

387 </TtlNtries>

388 <TtlCdtNtries>

389 <NbOfNtries>34</NbOfNtries>

390 <Sum>1234.56</Sum>

391 </TtlCdtNtries>

392 <TtlDbtNtries>

393 <NbOfNtries>0</NbOfNtries>

394 <Sum>0</Sum>

395 </TtlDbtNtries>

396 </TxsSummry>

397</Document>"""

398 data = xml_to_dict(xml_str.encode(), document_tag=True, array_tags=[], int_tags=['NbOfNtries'])

399 # print('')

400 # pprint(data)

401 self.assertEqual(data['Document']['TxsSummry']['TtlNtries']['NbOfNtries'], 12)

402 self.assertEqual(data['Document']['TxsSummry']['TtlCdtNtries']['NbOfNtries'], 34)

403

404 def test_per_delta(self):

405 begin = datetime(2017, 9, 17, 11, 42)

406 end = begin + timedelta(days=4)

407 ref = [(datetime(2017, 9, 17, 11, 42), datetime(2017, 9, 18, 11, 42)),

408 (datetime(2017, 9, 18, 11, 42), datetime(2017, 9, 19, 11, 42)),

409 (datetime(2017, 9, 19, 11, 42), datetime(2017, 9, 20, 11, 42)),

410 (datetime(2017, 9, 20, 11, 42), datetime(2017, 9, 21, 11, 42))]

411 res = per_delta(begin, end, timedelta(days=1))

412 self.assertEqual(list(res), ref)

413

414 def test_per_month(self):

415 begin = datetime(2017, 9, 1, 0, 0)

416 res = list(per_month(begin, begin + timedelta(days=32)))

417 ref = [(datetime(2017, 9, 1, 0, 0), datetime(2017, 10, 1, 0, 0)),

418 (datetime(2017, 10, 1, 0, 0), datetime(2017, 11, 1, 0, 0))]

419 self.assertEqual(list(res), ref)

420

421 def test_dates(self):

422 t = datetime(2018, 1, 30)

423 b, e = this_week(t)

424 self.assertEqual(b, pytz.utc.localize(datetime(2018, 1, 29)))

425 self.assertEqual(e, pytz.utc.localize(datetime(2018, 2, 5)))

426 b, e = this_month(t)

427 self.assertEqual(b, pytz.utc.localize(datetime(2018, 1, 1)))

428 self.assertEqual(e, pytz.utc.localize(datetime(2018, 2, 1)))

429 b, e = next_week(t)

430 self.assertEqual(b, pytz.utc.localize(datetime(2018, 2, 5)))

431 self.assertEqual(e, pytz.utc.localize(datetime(2018, 2, 12)))

432

433 def test_named_date_ranges(self):

434 t = datetime(2018, 5, 31)

435 t_tz = pytz.utc.localize(t)

436 named_ranges = [

437 ('last_month', last_month(t)),

438 ('last_year', last_year(t)),

439 ('this_month', this_month(t)),

440 ('last_week', last_week(t)),

441 ('yesterday', yesterday(t)),

442 ('today', yesterday(t + timedelta(hours=24))),

443 ]

444 day_ranges = [7, 15, 30, 60, 90]

445 for days in day_ranges:

446 named_ranges.append(

447 ('plus_minus_{}d'.format(days), (t_tz - timedelta(days=days), t_tz + timedelta(days=days))))

448 named_ranges.append(('prev_{}d'.format(days), (t_tz - timedelta(days=days), t_tz)))

449 named_ranges.append(('next_{}d'.format(days), (t_tz, t_tz + timedelta(days=days))))

450 for name, res in named_ranges:

451 # print('testing', name)

452 self.assertEqual(get_date_range_by_name(name, t), res)

453

454 def test_bank_info(self):

455 ac = 'FI8847304720017517'

456 inf = iban_bank_info(ac)

457 self.assertEqual(inf[0], 'POPFFI22')

458 self.assertEqual(inf[1], 'POP-Pankki')

459

460 ac = ''

461 inf = iban_bank_info(ac)

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

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

464

465 ac = 'BE75270187592710'

466 inf = iban_bank_info(ac)

467 self.assertEqual(inf[0], 'GEBABEBB')

468 self.assertEqual(inf[1], 'BNP Paribas Fortis')

469 ac = 'BE58465045170210'

470 inf = iban_bank_info(ac)

471 self.assertEqual(inf[0], 'KREDBEBB')

472 self.assertEqual(inf[1], 'KBC Bank')

473 ac = 'BE11000123456748'

474 inf = iban_bank_info(ac)

475 self.assertEqual(inf[0], 'BPOTBEB1')

476 self.assertEqual(inf[1], 'bpost bank')

477

478 def test_org_id_fi(self):

479 valids = [

480 'FI01098230',

481 'FI-01098230',

482 '0109823-0',

483 '2084069-9',

484 ]

485 invalids = [

486 '2084069-1',

487 'SE2084069-1',

488 ]

489 co_filtered = [

490 ('FI', 'FI01098230', '0109823-0'),

491 ('FI', 'FI-01098230', '0109823-0'),

492 ('FI', '0109823-0', '0109823-0'),

493 ('FI', '2084069-9', '2084069-9'),

494 ('SE', '01098230', '01098230'),

495 ('SE', '20840699', '20840699'),

496 ]

497 for cc, org, val in co_filtered:

498 self.assertEqual(filter_country_company_org_id(cc, org), val)

499 validate_country_company_org_id(cc, org)

500 for valid in valids:

501 fi_company_org_id_validator(valid)

502 for invalid in invalids:

503 try:

504 fi_company_org_id_validator(invalid)

505 self.fail('{} passed as valid FI-org'.format(invalid))

506 except ValidationError:

507 # print('ok')

508 pass

509 for n in range(10):

510 v0 = fi_company_org_id_generator()

511 # print(v0)

512 fi_company_org_id_validator(v0)

513

514 def test_reference_number_validators(self):

515 valid_fi_refs = [

516 '302300',

517 '202196',

518 '302290',

519 ]

520 for ref_no in valid_fi_refs:

521 fi_payment_reference_validator(ref_no)

522

523 invalid_fi_refs = [

524 '302301',

525 '202195',

526 '302291',

527 ]

528 for ref_no in invalid_fi_refs:

529 try:

530 fi_payment_reference_validator(ref_no)

531 self.assertFalse(True, '{} should have failed validation'.format(ref_no))

532 except ValidationError:

533 pass

534

535 valid_iso_refs = [

536 'RF92 1229',

537 'RF11 1232',

538 'RF48 1245',

539 ]

540 for ref_no in valid_iso_refs:

541 iso_payment_reference_validator(ref_no)

542

543 invalid_iso_refs = [

544 'RF92 1229}',

545 ]

546 for ref_no in invalid_iso_refs:

547 with self.assertRaisesMessage(ValidationError, 'Invalid payment reference'):

548 iso_payment_reference_validator(ref_no)

549

550 def test_fi_ssn_age(self):

551 samples = [

552 (date(2018, 12, 20), '231298-965X', 19),

553 (date(2018, 12, 22), '231298-965X', 19),

554 (date(2018, 12, 23), '231298-965X', 20),

555 (date(2018, 12, 24), '231298-965X', 20),

556 ]

557 for date_now, ssn, age in samples:

558 self.assertEqual(fi_ssn_age(ssn, date_now), age,

559 msg='{} age is {} on {} but fi_ssn_age result was {}'.format(ssn, age, date_now,

560 fi_ssn_age(ssn, date_now)))

561

562 def test_se_banks(self):

563 self.assertEqual(se_clearing_code_bank_info('6789'), ('Handelsbanken', 9))

564 se_iban_validator('SE45 5000 0000 0583 9825 7466')

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

566 se_iban_validator('')

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

568 se_iban_validator('XX45 5000 0000 0583 9825 7466')

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

570 se_iban_validator('SE45 5000 0000 0583 9825')

571 self.assertEqual(se_clearing_code_bank_info('9500'), ('Nordea AB', 10))

572 an = '957033025420'

573 bank_name, acc_digits = se_clearing_code_bank_info(an)

574 self.assertEqual(bank_name, 'Sparbanken Syd')

575 self.assertGreaterEqual(len(an) - 4, acc_digits)

576

577 def test_dk_banks(self):

578 an = 'DK50 0040 0440 1162 43'

579 dk_iban_validator(an)

580 bic, name = dk_iban_bank_info(an)

581 self.assertEqual(name, 'Nordea')

582 an = '8114 0008874093'

583 name = dk_clearing_code_bank_name(an)

584 self.assertEqual(name, 'Nykredit Bank')

585 an = 'DK2520006893703029'

586 name = dk_clearing_code_bank_name(an)

587 self.assertEqual(name, 'Nordea')

588

589 def test_ascii_filter(self):

590 pairs = [

591 ('Åke va Källe o Öring', 'Ake va Kalle o Oring'),

592 ('Tôi đang đi mua sắm', 'Toi ang i mua sam'),

593 ('HELÉN FRANZÉN', 'HELEN FRANZEN'),

594 ]

595 for a, b in pairs:

596 self.assertEqual(ascii_filter(a), b, 'ascii_filter("{}") != "{}"'.format(b, ascii_filter(a)))

597

598 def test_l10n(self):

599 from rest_framework.exceptions import ValidationError

600

601 with override('fi'):

602 msg = _("“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format.")

603 if 'muoto ei kelpaa' not in msg: 603 ↛ 604line 603 didn't jump to line 604, because the condition on line 603 was never true

604 print(msg)

605 self.assertTrue('muoto ei kelpaa' in msg)

606

607 try:

608 parse_bool('hello')

609 except ValidationError as e:

610 self.assertEqual(str(e),

611 "[ErrorDetail(string='hello ei ole yksi valittavissa olevista arvoista', code='invalid')]")

612

613 def test_sanitizers(self):

614 self.assertEqual(country_code_sanitizer('kods'), '')

615 self.assertEqual(country_code_sanitizer('fi'), 'FI')

616 self.assertEqual(phone_sanitizer('+13146094459'), '+13146094459')

617 self.assertEqual(phone_sanitizer('13146094459'), '13146094459')

618 self.assertEqual(phone_sanitizer('+13146094459A'), '+13146094459')

619 self.assertEqual(email_sanitizer('test@example.com'), 'test@example.com')

620 self.assertEqual(email_sanitizer('testexample.com'), '')

621

622 def test_dict_to_html(self):

623 a = {'b': 1, 'c': {'@testVariable': '123'}}

624 res = '<pre>B: 1\nC:\n Test variable: 123\n\n</pre>'

625 self.assertEqual(dict_to_html(a), res)

626

627 def test_format_xml(self):

628 assert settings.XMLLINT_PATH, 'add e.g. XMLLINT_PATH = "/usr/bin/xmllint" to settings.py'

629 src = '<ApplicationRequest> <CustomerId>1</CustomerId> <Command>DownloadFileList</Command><Timestamp>2019-11-27T04:32:18.613452+02:00</Timestamp><Environment>PRODUCTION</Environment></ApplicationRequest>'

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

631 dst = format_xml(src)

632 self.assertEqual(dst, dst_ref)

633 dst = format_xml_bytes(src.encode())

634 self.assertEqual(dst, dst_ref.encode())

635

636 def test_parse_sftp(self):

637 test_cases = [

638 ('sftp://jani@kajala.com', ['jani', None, 'kajala.com', '']),

639 ('sftp://jani.kajala:1231!@kajala.com', ['jani.kajala', '1231!', 'kajala.com', '']),

640 ('sftp://jani:1231!@kajala.com/my/dir', ['jani', '1231!', 'kajala.com', '/my/dir']),

641 ('sftp://jani.kajala:1231!@kajala.com/my/dir', ['jani.kajala', '1231!', 'kajala.com', '/my/dir']),

642 ]

643 for connection, ref_res in test_cases:

644 res = urlparse(connection)

645 res_list = [res.username, res.password, res.hostname, res.path]

646 self.assertListEqual(res_list, ref_res,

647 'SFTP connection string "{}" parsed incorrectly'.format(connection))

648

649 def test_admin(self):

650 obj = self.user

651 admin_log([obj], 'Hello, world')

652 admin_log([obj], 'Hello, world', user=self.user, ip='127.0.0.1')

653 admin_log(obj, 'Hello, world', user=self.user, ip='127.0.0.1')

654 e = LogEntry.objects.all().filter(object_id=obj.id).last()

655 self.assertIsNotNone(e)

656 assert isinstance(e, LogEntry)

657 self.assertEqual(e.change_message, 'Hello, world')

658 self.assertEqual(admin_obj_url(obj, 'admin:auth_user_change'), '/admin/auth/user/{}/change/'.format(obj.id))

659 self.assertEqual(admin_obj_url(None, 'admin:auth_user_change'), '')

660 self.assertEqual(admin_obj_link(None, 'admin:auth_user_change'), '')

661 self.assertEqual(admin_obj_url(obj), '/admin/auth/user/{}/change/'.format(obj.id))

662 self.assertEqual(admin_obj_url(e), '/admin/admin/logentry/{}/change/'.format(e.id))

663 link = admin_obj_link(obj, 'User', 'admin:auth_user_change')

664 self.assertEqual(link, "<a href='/admin/auth/user/{}/change/'>User</a>".format(obj.id))

665

666 def test_cmd_parser(self):

667 parser = CommandParser()

668 add_date_range_arguments(parser)

669 argv = parser.parse_args(['--begin', '2019-06-25', '--end', '2020-02-01'])

670 options = argv.__dict__

671 begin, end, steps = parse_date_range_arguments(options)

672 self.assertEqual(begin, pytz.utc.localize(datetime(2019, 6, 25)))

673 self.assertEqual(end, pytz.utc.localize(datetime(2020, 2, 1)))

674

675 def test_format_timedelta(self):

676 self.assertEqual(format_timedelta(timedelta(seconds=90)), '1min30s')

677 self.assertEqual(format_timedelta(timedelta(seconds=3600 + 90)), '1h1min30s')

678 self.assertEqual(format_timedelta(timedelta(seconds=90), minutes_label='min ', seconds_label='s '), '1min 30s')

679 self.assertEqual(

680 format_timedelta(timedelta(seconds=3600 + 90), hours_label='h ', minutes_label='min ', seconds_label='s '),

681 '1h 1min 30s')

682 self.assertEqual(format_timedelta(timedelta(seconds=90), seconds_label=''), '1min')

683

684 def test_dec123456(self):

685 self.assertEqual(dec1(Decimal('1.2345678')), Decimal('1.2'))

686 self.assertEqual(dec2(Decimal('1.2345678')), Decimal('1.23'))

687 self.assertEqual(dec3(Decimal('1.2345678')), Decimal('1.235'))

688 self.assertEqual(dec4(Decimal('1.2345678')), Decimal('1.2346'))

689 self.assertEqual(dec5(Decimal('1.2345678')), Decimal('1.23457'))

690 self.assertEqual(dec6(Decimal('1.2345678')), Decimal('1.234568'))

691

692 def test_model_funcs(self):

693 admin_log([self.user], 'test msg 1')

694 obj = LogEntry.objects.all().order_by('-pk').last()

695 assert isinstance(obj, LogEntry)

696 self.assertFalse(is_model_field_changed(obj, 'change_message'))

697 obj.change_message = 'hello world'

698 self.assertTrue(is_model_field_changed(obj, 'change_message'))

699 obj.save()

700 self.assertFalse(is_model_field_changed(obj, 'change_message'))

701 obj2 = clone_model(obj)

702 assert isinstance(obj2, LogEntry)

703 self.assertEqual(obj.change_message, obj2.change_message)

704 self.assertGreater(obj2.pk, obj.pk)

705 label, val = get_model_field_label_and_value(obj, 'action_time')

706 self.assertEqual(label, _('action time'))

707 self.assertEqual(str(obj.action_time), val)

708 obj_b = get_object_or_none(obj.__class__, id=obj.id)

709 self.assertEqual(obj_b.id, obj.id)

710 obj_b = get_object_or_none(obj.__class__, id=-1)

711 self.assertIsNone(obj_b)

712

713 def test_format_table(self):

714 a = [

715 ['date', 'description', 'count', 'unit price', 'total price'],

716 [date(2019, 12, 15), 'oranges', 1000, dec2('0.99'), dec2('990.00')],

717 [date(2020, 1, 3), 'apples', 4, dec2('1.10'), dec2('4.40')],

718 [date(2020, 11, 3), 'apples', 5, dec2('10.10'), dec2('50.50')],

719 ]

720 out = format_table(a, has_label_row=True, max_col=10)

721 out_ref = """

722---------------------------------------------------

723| date|descript..|count|unit price|total pr..|

724---------------------------------------------------

725|2019-12-15| oranges| 1000| 0.99| 990.00|

726|2020-01-03| apples| 4| 1.10| 4.40|

727|2020-11-03| apples| 5| 10.10| 50.50|

728---------------------------------------------------

729 """.strip()

730 self.assertEqual(out, out_ref)

731

732 out = format_table(a, has_label_row=True, max_col=20)

733 out_ref = """

734-----------------------------------------------------

735| date|description|count|unit price|total price|

736-----------------------------------------------------

737|2019-12-15| oranges| 1000| 0.99| 990.00|

738|2020-01-03| apples| 4| 1.10| 4.40|

739|2020-11-03| apples| 5| 10.10| 50.50|

740-----------------------------------------------------

741 """.strip()

742 self.assertEqual(out, out_ref)

743

744 out = format_table(a, has_label_row=True, max_col=20, col_sep=' | ')

745 out_ref = """

746-------------------------------------------------------------

747| date | description | count | unit price | total price|

748-------------------------------------------------------------

749|2019-12-15 | oranges | 1000 | 0.99 | 990.00|

750|2020-01-03 | apples | 4 | 1.10 | 4.40|

751|2020-11-03 | apples | 5 | 10.10 | 50.50|

752-------------------------------------------------------------

753 """.strip()

754 self.assertEqual(out, out_ref)

755

756 out = format_table(a, has_label_row=True, max_col=20, col_sep=' | ', left_align=[1])

757 out_ref = """

758-------------------------------------------------------------

759| date | description | count | unit price | total price|

760-------------------------------------------------------------

761|2019-12-15 | oranges | 1000 | 0.99 | 990.00|

762|2020-01-03 | apples | 4 | 1.10 | 4.40|

763|2020-11-03 | apples | 5 | 10.10 | 50.50|

764-------------------------------------------------------------

765 """.strip()

766 self.assertEqual(out, out_ref)

767

768 out = format_table(a, has_label_row=True, max_col=20, col_sep=' | ', left_align=[1], max_line=50)

769 out_ref = """

770-------------------------------------------------

771| date | description | count | unit price|..

772-------------------------------------------------

773|2019-12-15 | oranges | 1000 | 0.99|..

774|2020-01-03 | apples | 4 | 1.10|..

775|2020-11-03 | apples | 5 | 10.10|..

776-------------------------------------------------

777 """.strip()

778 self.assertEqual(out, out_ref)

779

780 out = format_table(a, left_align=[1], center_align=[0, 2, 3, 4], max_col=50)

781 out_ref = """

782-----------------------------------------------------

783| date |description|count|unit price|total price|

784|2019-12-15|oranges |1000 | 0.99 | 990.00 |

785|2020-01-03|apples | 4 | 1.10 | 4.40 |

786|2020-11-03|apples | 5 | 10.10 | 50.50 |

787-----------------------------------------------------

788 """.strip()

789 self.assertEqual(out, out_ref)

790

791 def test_ucfirst_lazy(self):

792 s = gettext_lazy(ucfirst_lazy("missing value"))

793 s_ref = gettext_lazy("Missing value")

794 s_en = "Missing value"

795 s_fi = "Puuttuva arvo"

796 with override('fi'):

797 self.assertEqual(s, s_fi)

798 self.assertEqual(s, s_ref)

799 with override('en'):

800 self.assertEqual(s, s_en)

801 self.assertEqual(s, s_ref)

802

803 def test_media_paths(self):

804 media_root1 = os.path.join(settings.MEDIA_ROOT, 'path1/path2')

805 media_root2 = os.path.join(settings.MEDIA_ROOT, 'path3') + '/'

806 test_paths = [

807 (os.path.join(media_root1, 'test1.file'), 'path1/path2/test1.file'),

808 (os.path.join('/diff/path', 'test1.file'), '/diff/path/test1.file'),

809 (os.path.join(media_root2, 'test1.file'), 'path3/test1.file'),

810 ]

811 for src, dst in test_paths:

812 self.assertEqual(strip_media_root(src), dst)

813 self.assertEqual(get_media_full_path(dst), src)

814

815 def test_end_of_month(self):

816 helsinki = pytz.timezone('Europe/Helsinki')

817 # 1

818 time_now = datetime(2020, 6, 5, 15, 47, 23, 818646)

819 eom = end_of_month(time_now, tz=helsinki)

820 eom_ref = helsinki.localize(datetime(2020, 6, 30, 23, 59, 59, 999999))

821 self.assertEqual(eom, eom_ref)

822 # 2

823 time_now = datetime(2020, 7, 5, 15, 47, 23, 818646)

824 eom = end_of_month(time_now, tz=helsinki)

825 eom_ref = helsinki.localize(datetime(2020, 7, 31, 23, 59, 59, 999999))

826 self.assertEqual(eom, eom_ref)

827 # 3

828 time_now = datetime(2020, 6, 5, 15, 47, 23, 818646)

829 eom = end_of_month(time_now, n=1, tz=helsinki)

830 eom_ref = helsinki.localize(datetime(2020, 7, 31, 23, 59, 59, 999999))

831 self.assertEqual(eom, eom_ref)

832 # 4

833 time_now = datetime(2020, 7, 5, 15, 47, 23, 818646)

834 eom = end_of_month(time_now, n=-2, tz=helsinki)

835 eom_ref = helsinki.localize(datetime(2020, 5, 31, 23, 59, 59, 999999))

836 self.assertEqual(eom, eom_ref)

837

838 def test_iban_generator_and_validator(self):

839 test_ibans = [

840 'MD7289912714638112731113',

841 'IS363252851674877586492113',

842 'HR5125000099152386224',

843 'CZ4750515755735423825528',

844 'FI3253811381259333',

845 'FR3212739000501869481882E94',

846 ]

847 for iban in test_ibans:

848 iban_validator(iban)

849 for cc in ['', 'FI', 'SE']:

850 for n in range(100):

851 acc = iban_generator(cc)

852 try:

853 iban_validator(acc)

854 except Exception as e:

855 print('iban_generator() returned', acc, 'but iban_validator() raised exception', e)

856 self.fail('iban_validator(iban_generator()) should not raise Exception, account number was {}'.format(acc))

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

858 iban_generator('XX')

859 with self.assertRaisesMessage(ValidationError, _('IBAN checksum generation does not support >26 character IBANs')):

860 iban_generator('AL')

861

862 def test_make_email_recipient(self):

863 email_tests = [

864 {

865 'list': [

866 ('Jani Kajala', 'kajala@example.com'),

867 '"Jani Kajala" <kajala@example.com>',

868 '<kajala@example.com>',

869 'kajala@example.com',

870 ],

871 'result': [

872 ('Jani Kajala', 'kajala@example.com'),

873 ('Jani Kajala', 'kajala@example.com'),

874 ('kajala@example.com', 'kajala@example.com'),

875 ('kajala@example.com', 'kajala@example.com'),

876 ]

877 }

878 ]

879 for et in email_tests:

880 res = make_email_recipient_list(et['list'])

881 self.assertListEqual(res, et['result'])

882

883 def test_choices(self):

884 val = choices_label(MY_CHOICES, MY_CHOICE_1)

885 self.assertEqual(val, 'MY_CHOICE_1')

886

887 def test_camel_case(self):

888 pairs = [

889 ('camelCaseWord', 'camel_case_word'),

890 ('camelCase', 'camel_case'),

891 ('camel', 'camel'),

892 ('camelCCase', 'camel_c_case'),

893 ]

894 for cc, us in pairs:

895 self.assertEqual(camel_case_to_underscore(cc), us)

896 self.assertEqual(cc, underscore_to_camel_case(us))

897

898 def create_dummy_request(self, path: str = '/admin/login/'):

899 request = request_factory.get(path)

900 request.user = self.user # type: ignore

901 request.user.profile = DummyUserProfile() # type: ignore

902 return request

903

904 def test_model_admin_base(self):

905 # test that actions sorting by name works

906 request = self.create_dummy_request()

907 user = self.user

908 model_admin = MyCustomAdmin(LogEntry, admin.site)

909 res = model_admin.get_actions(request)

910 self.assertEqual(list(res.items())[0][0], 'dummy_admin_func_a', 'ModelAdminBase.get_actions sorting failed')

911 self.assertEqual(list(res.items())[1][0], 'dummy_admin_func_b', 'ModelAdminBase.get_actions sorting failed')

912

913 # create 10 LogEntry for test user, 5 with text "VisibleLogMessage" and 5 "InvisibleLogMessage"

914 # then check that "VisibleLogMessage" log entries are not visible since max_history_length = 5

915 LogEntry.objects.filter(object_id=user.id).delete()

916 for n in range(5):

917 admin_log([user], 'VisibleLogMessage')

918 for n in range(5):

919 admin_log([user], 'InvisibleLogMessage')

920 self.assertEqual(LogEntry.objects.filter(object_id=user.id).count(), 10)

921 history_url = '/admin/auth/user/{}/history/'.format(user.id)

922 c = self.client

923 c.get(history_url, follow=True)

924 c.post('/admin/login/', {'username': 'test@example.com', 'password': 'test1234'})

925 res = c.get(history_url)

926 content = res.content.decode()

927 assert isinstance(content, str)

928 self.assertEqual(content.count('VisibleLogMessage'), 5)

929 self.assertEqual(content.count('InvisibleLogMessage'), 0)

930

931 def test_admin_log_entry_mixin(self):

932 user = self.user

933 AdminLogEntryMixin.fields_changed(user, ['username'], who=None)

934 e = LogEntry.objects.filter(object_id=user.id).last()

935 assert isinstance(e, LogEntry)

936 self.assertEqual(e.change_message, 'User id={}: username=test@example.com'.format(user.id))

937

938 def test_admin_file_download_mixin(self):

939 class MyModel(models.Model):

940 file = models.FileField(upload_to='uploads')

941

942 model_admin = MyCustomAdmin(MyModel, admin.site)

943 self.assertListEqual(model_admin.get_file_fields(), ['file'])

944 self.assertEqual(model_admin.single_file_field, 'file')

945 self.assertEqual(len(model_admin.get_download_urls()), 2)

946 res = model_admin.file_download_view(self.create_dummy_request(), 'requirements.txt')

947 self.assertTrue(isinstance(res, FileSystemFileResponse))

948

949 def test_auth(self):

950 req = self.create_dummy_request()

951 require_auth(req) # type: ignore

952 req.user = None

953 self.assertIsNone(require_auth(req, exceptions=False))

954 try:

955 require_auth(req) # type: ignore

956 self.fail('require_auth fail')

957 except NotAuthenticated:

958 pass

959 try:

960 model_admin = AuthUserMixin()

961 model_admin.request = req

962 user = model_admin.auth_user

963 self.fail('require_auth fail')

964 except NotAuthenticated:

965 pass

966

967 def test_middleware(self):

968 # EnsureOriginMiddleware

969 request = self.create_dummy_request('/admin/login/')

970 EnsureOriginMiddleware(dummy_middleware_get_response)(request)

971 self.assertIn('HTTP_ORIGIN', request.META)

972 self.assertEqual(request.META['HTTP_ORIGIN'], request.get_host())

973

974 # LogExceptionMiddleware

975 dummy_log = DummyLogHandler()

976 jutil_middleware_logger.addHandler(dummy_log)

977 try:

978 raise Exception('Dummy exception, ignore this')

979 except Exception as e:

980 mw = LogExceptionMiddleware(dummy_middleware_get_response)

981 mw.process_exception(request, e)

982 msg = dummy_log.msgs.pop()

983 self.assertIn('Exception: Dummy exception, ignore this', msg)

984 self.assertIn('user=test@example.com', msg)

985 jutil_middleware_logger.removeHandler(dummy_log)

986

987 # EnsureLanguageCookieMiddleware

988 lang_cookie_tests = [

989 ('/admin/login/', settings.LANGUAGE_CODE),

990 ('/admin/login/?django_language=fi', 'fi'),

991 ]

992 for request_path, lang_code in lang_cookie_tests:

993 request = self.create_dummy_request(request_path)

994 mw = EnsureLanguageCookieMiddleware(dummy_middleware_get_response)

995 res = mw(request)

996 assert isinstance(res, HttpResponse)

997 self.assertEqual(res.status_code, 200)

998 self.assertIn('django_language', request.COOKIES)

999 self.assertIn(request.COOKIES['django_language'], lang_code)

1000 self.assertIn('django_language', res.cookies)

1001 self.assertEqual(str(res.cookies['django_language']), 'Set-Cookie: django_language={}; Path=/'.format(lang_code))

1002

1003 # ActivateUserProfileTimezoneMiddleware

1004 user = self.user

1005 self.assertTrue(user.is_authenticated)

1006 user.profile.timezone = 'Europe/Helsinki'

1007 with timezone.override(pytz.timezone('America/Chicago')):

1008 request = self.create_dummy_request('/admin/login/')

1009 mw = ActivateUserProfileTimezoneMiddleware(dummy_time_zone_response)

1010 res = mw(request)

1011 content = res.content.decode()

1012 self.assertEqual(content, user.profile.timezone)

1013

1014 def test_safe_fields(self):

1015 class TestModel(models.Model):

1016 cf = SafeCharField(max_length=256)

1017 tf = SafeTextField()

1018

1019 obj = TestModel()

1020 data = 'hello world <script>alert("popup")<script>'

1021 data_ref = 'hello world '

1022 for f in obj._meta.fields:

1023 if f.name in ['cf', 'tf']:

1024 f.save_form_data(obj, data)

1025 self.assertEqual(obj.cf, data_ref)

1026 self.assertEqual(obj.tf, data_ref)

1027

1028 def test_responses(self):

1029 a = [

1030 ['date', 'description', 'count', 'unit price', 'total price'],

1031 [date(2019, 12, 15), 'oranges', 1000, dec2('0.99'), dec2('990.00')],

1032 [date(2020, 1, 3), 'apples', 4, dec2('1.10'), dec2('4.40')],

1033 [date(2020, 11, 3), 'apples', 5, dec2('10.10'), dec2('50.50')],

1034 ]

1035 res = CsvResponse(a, 'test.csv')

1036 content_ref = b'date,description,count,unit price,total price\r\n2019-12-15,oranges,1000,0.99,990.00\r\n2020-01-03,apples,4,1.10,4.40\r\n2020-11-03,apples,5,10.10,50.50\r\n'

1037 self.assertEqual(content_ref, res.content)

1038 print(res.content.decode())

1039

1040 def test_wait_object_or_none(self):

1041 admin_log([self.user], 'Hello, world')

1042 e = LogEntry.objects.all().filter(object_id=self.user.id).last()

1043 assert isinstance(e, LogEntry)

1044 e_id = e.id

1045 obj = wait_object_or_none(LogEntry, id=e_id)

1046 self.assertIsNotNone(obj)

1047 self.assertEqual(obj.id, e_id)

1048 t0 = now()

1049 obj = wait_object_or_none(LogEntry, timeout=1.0, sleep_interval=0.1, id=e_id+1)

1050 t1 = now()

1051 self.assertIsNone(obj)

1052 self.assertGreater(t1-t0, timedelta(seconds=0.99))

1053

1054

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

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

1057

1058 admin.site.unregister(User)

1059 admin.site.register(User, MyCustomAdmin)

1060 admin.site.register(LogEntry, MyCustomAdmin)