def test_progress_info_without_desc(self): sleep_mock = SleepMock() offset = datetime.timedelta(seconds=1) with mock.patch.object(timezone, 'now', MockDatetimeGenerator(offset=offset)),\ mock.patch.object(time, 'sleep', sleep_mock): linear_processing_task(desc=None, total=3) # Add the last entry, executed after time.sleep() so not captured, yet: sleep_mock.add_iteration() info = sleep_mock.progress_info assert info == [ [0, 1.0, 'linear_processing_task: 0/3it 0% 0/it (Main task)'], [ 1, 2.0, 'linear_processing_task: 1/3it 33% 2.0\xa0seconds/it (Main task)' ], [ 2, 3.0, 'linear_processing_task: 2/3it 67% 1.5\xa0seconds/it (Main task)' ], [ 3, 6.0, 'linear_processing_task: 3/3it 100% 2.0\xa0seconds/it finished (Main task)' ], ]
def test_linear_processing_task(self): sleep_mock = SleepMock() offset = datetime.timedelta(seconds=1) with mock.patch.object(timezone, 'now', MockDatetimeGenerator(offset=offset)),\ mock.patch.object(time, 'sleep', sleep_mock): linear_processing_task(desc='Foo Bar', total=10) signals = list( SignalInfoModel.objects.values_list('signal_name', flat=True)) assert signals == ['executing', 'complete'] # Add the last entry, executed after time.sleep() so not captured, yet: sleep_mock.add_iteration() info = sleep_mock.progress_info assert info == [ [0, 1.0, 'Foo Bar: 0/10it 0% 0/it (Main task)'], [1, 2.0, 'Foo Bar: 1/10it 10% 2.0\xa0seconds/it (Main task)'], [2, 3.0, 'Foo Bar: 2/10it 20% 1.5\xa0seconds/it (Main task)'], [3, 4.0, 'Foo Bar: 3/10it 30% 1.3\xa0seconds/it (Main task)'], [4, 5.0, 'Foo Bar: 4/10it 40% 1.2\xa0seconds/it (Main task)'], [5, 6.0, 'Foo Bar: 5/10it 50% 1.2\xa0seconds/it (Main task)'], [6, 7.0, 'Foo Bar: 6/10it 60% 1.2\xa0seconds/it (Main task)'], [7, 8.0, 'Foo Bar: 7/10it 70% 1.1\xa0seconds/it (Main task)'], [8, 9.0, 'Foo Bar: 8/10it 80% 1.1\xa0seconds/it (Main task)'], [9, 10.0, 'Foo Bar: 9/10it 90% 1.1\xa0seconds/it (Main task)'], [ 10, 13.0, 'Foo Bar: 10/10it 100% 1.3\xa0seconds/it finished (Main task)' ], ]
def test_parallel_task(self): assert TaskModel.objects.count() == 0 class TimeSleepNoop: def __call__(self, *args, **kwargs): pass offset = datetime.timedelta(seconds=1) with mock.patch.object(timezone, 'now', MockDatetimeGenerator(offset=offset)),\ mock.patch.object(time, 'sleep', TimeSleepNoop()): task_result = parallel_task(total=10, task_num=2) assert TaskModel.objects.count() == 3 assert isinstance(task_result, Result) main_task_id = task_result.task.id main_task_instance = TaskModel.objects.get(pk=main_task_id) assert main_task_instance.human_progress_string() == ( '10/10it 100% 2.5 seconds/it finished') assert str(main_task_instance) == ( 'parallel_task: 10/10it 100% 2.5 seconds/it finished (Main task)') sub_tasks = TaskModel.objects.filter( parent_task=main_task_instance).order_by('update_dt') values = list(sub_tasks.values_list('name', 'state__signal_name')) assert values == [('parallel_sub_task', 'complete'), ('parallel_sub_task', 'complete')] # Note: Huey is in immediate mode, so the tasks executes synchronously! sub_tasks1 = sub_tasks[0] assert sub_tasks1.human_progress_string( ) == '5/5it 100% 1.8 seconds/it finished' assert str(sub_tasks1) == ( 'parallel_sub_task: 5/5it 100% 1.8 seconds/it finished (Sub task of parallel_task)' ) sub_tasks2 = sub_tasks[1] assert sub_tasks2.human_progress_string( ) == '5/5it 100% 1.8 seconds/it finished' assert str(sub_tasks2) == ( 'parallel_sub_task: 5/5it 100% 1.8 seconds/it finished (Sub task of parallel_task)' )
def test_progress_info_without_total(self): sleep_mock = SleepMock() offset = datetime.timedelta(seconds=1) with mock.patch.object(timezone, 'now', MockDatetimeGenerator(offset=offset)),\ mock.patch.object(time, 'sleep', sleep_mock): linear_processing_task(desc='Without total', total=3, no_total=True) # Add the last entry, executed after time.sleep() so not captured, yet: sleep_mock.add_iteration() info = sleep_mock.progress_info assert info == [ [0, 1.0, 'Without total: 0it 0/it (Main task)'], [1, 2.0, 'Without total: 1it 2.0\xa0seconds/it (Main task)'], [2, 3.0, 'Without total: 2it 1.5\xa0seconds/it (Main task)'], [ 3, 6.0, 'Without total: 3it 2.0\xa0seconds/it finished (Main task)' ], ]
def test_set_name_by_request(self): with self.assertLogs('django_tools'): item = baker.make(ItemModel) link = ItemLinkModel(item=item, url='http://test.tld/foo/bar') offset = datetime.timedelta(seconds=30) with patch.object(timezone, 'now', MockDatetimeGenerator(offset)): with requests_mock.Mocker() as m: m.get('http://test.tld/foo/bar', text='No title') assert link.last_check is None with self.assertLogs('inventory.models.links', level=logging.WARNING) as logs: link.full_clean() assert link.page_title is None assert link.name is None assert link.last_check == parse_dt( '2000-01-01T00:00:30+0000') logs = logs.output assert logs == [ "WARNING:inventory.models.links:No title found in 'http://test.tld/foo/bar'" ] # We should not create request on every admin save call with self.assertLogs('inventory.models.links', level=logging.DEBUG) as logs: link.full_clean() assert link.page_title is None assert link.name is None logs = logs.output assert logs == [ 'DEBUG:inventory.models.links:Last check is 0:00:30 ago.', "INFO:inventory.models.links:Skip request for: 'http://test.tld/foo/bar'" ] # Next try after 1 Min m.get('http://test.tld/foo/bar', text='<title>A <boom>Title</boom>!</title>') with self.assertLogs('inventory.models.links', level=logging.INFO) as logs: link.full_clean() assert link.page_title == 'A Title!' assert link.name == 'A Title!' logs = logs.output assert logs == [ "INFO:inventory.models.links:Found title: 'A <boom>Title</boom>!'" ] # Don't make requests, if we have a link name! with requests_mock.Mocker(): with self.assertLogs('inventory.models.links', level=logging.DEBUG) as logs: link.full_clean() assert link.page_title == 'A Title!' assert link.name == 'A Title!' logs = logs.output assert logs == [ ("DEBUG:inventory.models.links:Skip link request:" " because we have a name: 'A Title!'") ]
class ModelManipulateTestCase(TestCase): @mock.patch.object(timezone, 'now', MockDatetimeGenerator()) def test_create_or_update(self): # create a new entry: with AssertModelCleanCalled() as cm: instance, created, updated_fields = create_or_update( ModelClass=CreateOrUpdateTestModel, lookup={'id': 1}, name='First entry', slug='first' ) assert isinstance(instance, CreateOrUpdateTestModel) assert instance.id == 1 assert instance.name == 'First entry' assert instance.slug == 'first' assert instance.create_dt == parse_dt('2001-01-01T00:00:00+0000') assert instance.update_dt == parse_dt('2001-01-01T00:00:00+0000') assert created is True assert updated_fields is None cm.assert_no_missing_cleans() # Change only 'slug' with AssertModelCleanCalled() as cm: instance, created, updated_fields = create_or_update( ModelClass=CreateOrUpdateTestModel, lookup={'id': 1}, name='First entry', slug='change-value' ) assert isinstance(instance, CreateOrUpdateTestModel) assert instance.id == 1 assert instance.name == 'First entry' assert instance.slug == 'change-value' assert instance.create_dt == parse_dt('2001-01-01T00:00:00+0000') # not changed! assert instance.update_dt == parse_dt('2002-01-01T00:00:00+0000') assert created is False assert updated_fields == ['slug'] cm.assert_no_missing_cleans() # Change 'name' and 'slug': with AssertModelCleanCalled() as cm: instance, created, updated_fields = create_or_update( ModelClass=CreateOrUpdateTestModel, lookup={'id': 1}, name='New name !', slug='new-slug' ) assert isinstance(instance, CreateOrUpdateTestModel) assert instance.id == 1 assert instance.name == 'New name !' assert instance.slug == 'new-slug' assert instance.create_dt == parse_dt('2001-01-01T00:00:00+0000') # not changed! assert instance.update_dt == parse_dt('2003-01-01T00:00:00+0000') assert created is False assert updated_fields == ['name', 'slug'] cm.assert_no_missing_cleans() # Nothing changed: instance, created, updated_fields = create_or_update( ModelClass=CreateOrUpdateTestModel, lookup={'id': 1}, name='New name !', slug='new-slug' ) assert isinstance(instance, CreateOrUpdateTestModel) assert instance.id == 1 assert instance.name == 'New name !' assert instance.slug == 'new-slug' assert instance.create_dt == parse_dt('2001-01-01T00:00:00+0000') assert instance.update_dt == parse_dt('2003-01-01T00:00:00+0000') # not changed! assert created is False assert updated_fields == [] def test_non_valid(self): msg = str(validate_slug.message) with self.assertRaisesMessage(ValidationError, msg): create_or_update( ModelClass=CreateOrUpdateTestModel, lookup={'id': 1}, name='foo', slug='this is no Slug !' ) # Update existing entry with non-valid values should also not work: CreateOrUpdateTestModel(id=1, name='foo', slug='bar') with self.assertRaisesMessage(ValidationError, msg): create_or_update( ModelClass=CreateOrUpdateTestModel, lookup={'id': 1}, name='foo', slug='this is no Slug !' ) def test_disable_full_clean(self): # Create a new entry without "full_clean()" call: with AssertModelCleanCalled() as cm: instance, created, updated_fields = create_or_update( ModelClass=CreateOrUpdateTestModel, lookup={'id': 1}, call_full_clean=False, slug='This is not a valid slug!' ) assert isinstance(instance, CreateOrUpdateTestModel) assert instance.id == 1 assert instance.slug == 'This is not a valid slug!' assert created is True assert updated_fields is None assert cm.called_cleans == [] assert len(cm.missing_cleans) == 1 # Change existing without "full_clean()" call: with AssertModelCleanCalled() as cm: instance, created, updated_fields = create_or_update( ModelClass=CreateOrUpdateTestModel, lookup={'id': 1}, call_full_clean=False, slug='Also no valid slug!' ) assert isinstance(instance, CreateOrUpdateTestModel) assert instance.id == 1 assert instance.slug == 'Also no valid slug!' assert created is False assert updated_fields == ['slug'] assert cm.called_cleans == [] assert len(cm.missing_cleans) == 1 @mock.patch.object(timezone, 'now', MockDatetimeGenerator()) def test_create_or_update_without_lookup(self): # create a new entry: with AssertModelCleanCalled() as cm: instance, created, updated_fields = create_or_update( ModelClass=CreateOrUpdateTestModel, lookup=None, name='First entry', slug='first' ) assert isinstance(instance, CreateOrUpdateTestModel) assert instance.pk is not None assert instance.name == 'First entry' assert instance.slug == 'first' assert instance.create_dt == parse_dt('2001-01-01T00:00:00+0000') assert instance.update_dt == parse_dt('2001-01-01T00:00:00+0000') assert created is True assert updated_fields is None cm.assert_no_missing_cleans() @mock.patch.object(timezone, 'now', MockDatetimeGenerator()) def test_create(self): # create a new entry: with AssertModelCleanCalled() as cm: instance = create( ModelClass=CreateOrUpdateTestModel, name='First entry', slug='first' ) assert isinstance(instance, CreateOrUpdateTestModel) assert instance.pk is not None assert instance.name == 'First entry' assert instance.slug == 'first' assert instance.create_dt == parse_dt('2001-01-01T00:00:00+0000') assert instance.update_dt == parse_dt('2001-01-01T00:00:00+0000') cm.assert_no_missing_cleans()
def test_normal_user_create_minimal_item(self): offset = datetime.timedelta(minutes=1) with mock.patch.object(timezone, 'now', MockDatetimeGenerator(offset=offset)),\ mock.patch.object(NowNode, 'render', return_value='MockedNowNode'), \ mock.patch.object(CsrfTokenNode, 'render', return_value='MockedCsrfTokenNode'): self.client.force_login(self.normaluser) response = self.client.get('/admin/inventory/itemmodel/add/') assert response.status_code == 200 self.assert_html_parts( response, parts=( f'<title>Add Item | PyInventory v{__version__}</title>', )) assert_html_response_snapshot(response=response, validate=False) assert ItemModel.objects.count() == 0 post_data = dict(ITEM_FORM_DEFAULTS) response = self.client.post( path='/admin/inventory/itemmodel/add/', data=post_data, ) assert response.status_code == 302, response.content.decode( 'utf-8') # Form error? self.assertRedirects(response, expected_url='/admin/inventory/itemmodel/') data = list( ItemModel.objects.values_list('kind__name', 'name', 'version')) assert data == [('kind', 'name', 2) ] # FIXME: Save call done two times! item = ItemModel.objects.first() self.assert_messages( response, expected_messages=[ f'The Item “<a href="/admin/inventory/itemmodel/{item.pk}/change/"> - name</a>”' ' was added successfully.' ]) assert item.user_id == self.normaluser.pk # Test django-tools VersionProtectBaseModel integration: assert item.version == 2 # current Version in DB post_data['version'] = 1 # Try to overwrite with older version post_data['name'] = 'A new Name!' response = self.client.post( path=f'/admin/inventory/itemmodel/{item.pk}/change/', data=post_data, ) self.assert_html_parts( response, parts= (f'<title>Change Item | PyInventory v{__version__}</title>', '<li>Version error: Overwrite version 2 with 1 is forbidden!</li>', '<pre>- "name"\n+ "A new Name!"</pre>')) html = response.content.decode('utf-8') html = html.replace(str(item.pk), '<removed-UUID>') assert_html_snapshot(got=html, validate=False)
def test_auto_group_items(self): self.client.force_login(self.normaluser) offset = datetime.timedelta(minutes=1) with mock.patch.object(timezone, 'now', MockDatetimeGenerator(offset=offset)): for main_item_no in range(1, 3): main_item = ItemModel.objects.create( id=f'00000000-000{main_item_no}-0000-0000-000000000000', user=self.normaluser, name=f'main item {main_item_no}') main_item.full_clean() for sub_item_no in range(1, 3): sub_item = ItemModel.objects.create( id= f'00000000-000{main_item_no}-000{sub_item_no}-0000-000000000000', user=self.normaluser, parent=main_item, name=f'sub item {main_item_no}.{sub_item_no}') sub_item.full_clean() names = list( ItemModel.objects.order_by('id').values_list('name', flat=True)) assert names == [ 'main item 1', 'sub item 1.1', 'sub item 1.2', 'main item 2', 'sub item 2.1', 'sub item 2.2', ] # Default mode, without any GET parameter -> group "automatic": with mock.patch.object(NowNode, 'render', return_value='MockedNowNode'), \ mock.patch.object(CsrfTokenNode, 'render', return_value='MockedCsrfTokenNode'), \ self.assertLogs(logger='inventory', level=logging.DEBUG) as logs: response = self.client.get(path='/admin/inventory/itemmodel/', ) assert response.status_code == 200 self.assert_html_parts( response, parts=( f'<title>Select Item to change | PyInventory v{__version__}</title>', '<a href="/admin/inventory/itemmodel/00000000-0001-0000-0000-000000000000/change/">' 'main item 1</a>', '<li><a href="/admin/inventory/itemmodel/00000000-0001-0001-0000-000000000000/change/">' 'sub item 1.1</a></li>', )) assert logs.output == [ 'INFO:inventory.admin.item:Group items: True (auto mode: True)', 'DEBUG:inventory.admin.item:Display sub items inline', 'DEBUG:inventory.admin.item:Display sub items inline' ] assert_html_response_snapshot(response=response, validate=False) # Search should disable grouping: with mock.patch.object(NowNode, 'render', return_value='MockedNowNode'), \ mock.patch.object(CsrfTokenNode, 'render', return_value='MockedCsrfTokenNode'), \ self.assertLogs(logger='inventory', level=logging.DEBUG) as logs: response = self.client.get( path='/admin/inventory/itemmodel/?q=sub+item+2.', ) assert response.status_code == 200 self.assert_html_parts( response, parts=( '<input type="text" size="40" name="q" value="sub item 2." id="searchbar" autofocus>', '2 results (<a href="?">6 total</a>)', '<a href="/admin/inventory/itemmodel/00000000-0002-0001-0000-000000000000/change/">' 'sub item 2.1</a>', '<a href="/admin/inventory/itemmodel/00000000-0002-0002-0000-000000000000/change/">' 'sub item 2.2</a>', )) assert logs.output == [ # grouping disabled? 'INFO:inventory.admin.item:Group items: False (auto mode: True)' ] assert_html_response_snapshot(response=response, validate=False)