Coverage for jutil/tests.py : 97%

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 logging
2import os
3from datetime import datetime, timedelta, date
4from decimal import Decimal
5from os.path import join
6from pprint import pprint
7from urllib.parse import urlparse
8from django.utils import timezone
9from typing import List
11from django.utils.timezone import now
13from jutil.modelfields import SafeCharField, SafeTextField
14from jutil.middleware import logger as jutil_middleware_logger, ActivateUserProfileTimezoneMiddleware
15import pytz
16from django.conf import settings
17from django.contrib.admin.models import LogEntry
18from django.contrib.auth.models import User
19from django.core.exceptions import ValidationError
20from django.core.management.base import CommandParser # type: ignore
21from django.db import models
22from django.http.response import HttpResponse
23from django.test import TestCase
24from django.test.client import RequestFactory, Client
25from django.utils.translation import override, gettext as _, gettext_lazy
26from rest_framework.exceptions import NotAuthenticated
27from jutil.admin import admin_log, admin_obj_url, admin_obj_link, ModelAdminBase, AdminLogEntryMixin, \
28 AdminFileDownloadMixin
29from jutil.auth import require_auth, AuthUserMixin
30from jutil.command import get_date_range_by_name, add_date_range_arguments, parse_date_range_arguments
31from jutil.dict import dict_to_html, choices_label
32from jutil.email import make_email_recipient_list
33from jutil.middleware import EnsureOriginMiddleware, LogExceptionMiddleware, EnsureLanguageCookieMiddleware
34from jutil.model import is_model_field_changed, clone_model, get_model_field_label_and_value, get_object_or_none, \
35 wait_object_or_none
36from jutil.request import get_ip_info
37from jutil.responses import FileSystemFileResponse, CsvResponse
38from jutil.testing import DefaultTestSetupMixin
39from jutil.urls import url_equals, url_mod, url_host
40from jutil.xml import xml_to_dict, dict_to_element, _xml_filter_tag_name
41from 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
43from 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
46from jutil.parse import parse_datetime, parse_bool
47from 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
55from xml.etree.ElementTree import Element
56from xml.etree import ElementTree as ET
57from django.contrib import admin
60MY_CHOICE_1 = '1'
61MY_CHOICE_2 = '2'
62MY_CHOICES = (
63 (MY_CHOICE_1, 'MY_CHOICE_1'),
64 (MY_CHOICE_2, 'MY_CHOICE_2'),
65)
67request_factory = RequestFactory()
70class DummyLogHandler(logging.Handler):
71 msgs: List[str]
73 def __init__(self):
74 super().__init__()
75 self.msgs = []
77 def emit(self, record):
78 msg = self.format(record)
79 self.msgs.append(msg)
82def dummy_time_zone_response(obj) -> HttpResponse:
83 tz = timezone.get_current_timezone()
84 return HttpResponse(str(tz).encode())
87def dummy_admin_func_a(modeladmin, request, qs):
88 print('dummy_admin_func_a')
91def dummy_admin_func_b(modeladmin, request, qs):
92 print('dummy_admin_func_b')
95def dummy_middleware_get_response(obj) -> HttpResponse:
96 return HttpResponse(b'hello content')
99class DummyUserProfile:
100 timezone = 'Europe/Helsinki'
103class MyCustomAdmin(ModelAdminBase, AdminFileDownloadMixin):
104 max_history_length = 5
105 actions = (
106 dummy_admin_func_b,
107 dummy_admin_func_a,
108 )
110 def get_object(self, request, obj_id):
111 return self.model.objects.get(id=obj_id)
113 def get_object_by_filename(self, request, filename):
114 return User.objects.first() # dummy return for test_admin_file_download_mixin
117class 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()
127 def tearDown(self):
128 super().setUp()
130 def test_payment_reference(self):
131 self.assertEqual(fi_payment_reference_number('100'), '1009')
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
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')
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')
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'), '')
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')
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')
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'), '')
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')
211 def test_validate_country_iban(self):
212 with self.assertRaisesMessage(ValidationError, _('Invalid IBAN account number')):
213 validate_country_iban('FI15616515616156', 'SE')
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)
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(''), '')
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'))
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))
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')
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')
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)
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)
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)
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)
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)
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)
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)
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)
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)))
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)
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')
460 ac = ''
461 inf = iban_bank_info(ac)
462 self.assertEqual(inf[0], '')
463 self.assertEqual(inf[1], '')
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')
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)
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)
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
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)
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)
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)))
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)
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')
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)))
598 def test_l10n(self):
599 from rest_framework.exceptions import ValidationError
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)
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')]")
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'), '')
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)
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())
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))
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))
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)))
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')
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'))
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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')
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'])
883 def test_choices(self):
884 val = choices_label(MY_CHOICES, MY_CHOICE_1)
885 self.assertEqual(val, 'MY_CHOICE_1')
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))
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
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')
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)
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))
938 def test_admin_file_download_mixin(self):
939 class MyModel(models.Model):
940 file = models.FileField(upload_to='uploads')
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))
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
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())
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)
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))
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)
1014 def test_safe_fields(self):
1015 class TestModel(models.Model):
1016 cf = SafeCharField(max_length=256)
1017 tf = SafeTextField()
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)
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())
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))
1055dummy_admin_func_a.short_description = 'A' # type: ignore
1056dummy_admin_func_b.short_description = 'B' # type: ignore
1058admin.site.unregister(User)
1059admin.site.register(User, MyCustomAdmin)
1060admin.site.register(LogEntry, MyCustomAdmin)