def setUp(self): super().setUp() setup_models(self) self.worker = Worker.objects.get(user__username='******') self.worker.user.is_staff = True self.worker.user.save() self.request_client.login(username=self.worker.user.username, password='******') self.full_todo_list_template = TodoListTemplateFactory( slug='full-template-slug', name='Full template name', description='Full template description', conditional_property_function={ 'path': 'orchestra.tests.test_todos' '._get_test_conditional_props' }, todos=TODO_TEMPLATE_NESTED_TODOS) self.empty_todo_list_template = TodoListTemplateFactory( slug='empty-template-slug', name='Empty template name', description='Empty template description', conditional_property_function={ 'path': 'orchestra.tests.test_todos' '._get_test_conditional_props' }, todos={}) self.import_url_name = ( 'orchestra:todos:import_todo_list_template_from_spreadsheet') self.template_admin_name = 'admin:orchestra_todolisttemplate_change' self.export_url_name = 'admin:orchestra_todolisttemplate_actions'
def test_get_todo_templates(self, mock_request): # This converts `requests.get` into DRF's `APIClient.get` # To make it testable def get(url, *args, **kwargs): return_value = self.request_client.get(url, format='json') return_value.text = json.dumps(return_value.data) return return_value mock_request.get = get template1 = TodoListTemplateFactory() template2 = TodoListTemplateFactory() # Get template1 and template2 res = get_todo_templates() self.assertEqual(len(res), 2) expected_ids = [template1.id, template2.id] for r in res: self.assertIn(r['id'], expected_ids) # # Get newly created template3 template3 = TodoListTemplateFactory() res = get_todo_templates() self.assertEqual(len(res), 3) self.assertEqual(res[2]['id'], template3.id)
def test_update_todos_from_todolist_template_success(self): num_todos = Todo.objects.all().count() update_todos_from_todolist_template_url = \ reverse('orchestra:todos:update_todos_from_todolist_template') todolist_template = TodoListTemplateFactory( slug=self.todolist_template_slug, name=self.todolist_template_name, description=self.todolist_template_description, todos={ 'items': [{ 'id': 1, 'description': 'todo parent', 'project': self.project.id, 'step': self.step.slug, 'items': [{ 'id': 2, 'project': self.project.id, 'step': self.step.slug, 'description': 'todo child', 'items': [] }] }] }, ) resp = self.request_client.post( update_todos_from_todolist_template_url, { 'todolist_template': todolist_template.slug, 'project': self.project.id, 'step': self.step.slug }) self.assertEqual(resp.status_code, 200) self.assertEqual(Todo.objects.all().count(), num_todos + 3) todos = load_encoded_json(resp.content) expected_todos = [ _todo_data('todo child', False, template=todolist_template.id, parent_todo=todos[1]['id'], project=self.project.id, step=self.step.slug), _todo_data('todo parent', False, template=todolist_template.id, parent_todo=todos[2]['id'], project=self.project.id, step=self.step.slug), _todo_data(self.todolist_template_name, False, template=todolist_template.id, project=self.project.id, step=self.step.slug), ] for todo, expected_todo in zip(todos, expected_todos): self._verify_todo_content(todo, expected_todo)
def test_update_todos_from_todolist_template_missing_task_id(self): update_todos_from_todolist_template_url = \ reverse('orchestra:todos:update_todos_from_todolist_template') todolist_template = TodoListTemplateFactory( slug=self.todolist_template_slug, name=self.todolist_template_name, description=self.todolist_template_description, todos={'items': []}, ) resp = self.request_client.post( update_todos_from_todolist_template_url, {'todolist_template': todolist_template.slug}) self.assertEqual(resp.status_code, 403)
def test_update_todos_from_todolist_template_invalid_project(self): update_todos_from_todolist_template_url = \ reverse('orchestra:todos:update_todos_from_todolist_template') todolist_template = TodoListTemplateFactory( slug=self.todolist_template_slug, name=self.todolist_template_name, description=self.todolist_template_description, todos={'items': []}, ) resp = self.request_client.post( update_todos_from_todolist_template_url, { 'todolist_template': todolist_template.slug, 'project': self.project2.id, 'step': self.step.id }) self.assertEqual(resp.status_code, 403)
def test_create_todos_from_template(self, mock_request): # This converts `requests.post` into DRF's `APIClient.post` # To make it testable def post(url, *args, **kwargs): kw = kwargs.get('data', '') data = json.loads(kw) return_value = self.request_client.post(url, data, format='json') return_value.text = json.dumps(return_value.json()) return return_value todolist_template = TodoListTemplateFactory( slug=self.todolist_template_slug, name=self.todolist_template_name, description=self.todolist_template_description, todos={ 'items': [{ 'id': 1, 'description': 'todo parent', 'project': self.project.id, 'step': self.step.slug, 'items': [{ 'id': 2, 'project': self.project.id, 'step': self.step.slug, 'description': 'todo child', 'items': [] }] }] }, ) mock_request.post = post additional_data = {'some_additional_data': 'value'} result = create_todos_from_template(self.todolist_template_slug, self.project.id, self.step.slug, additional_data) self.assertEqual(result['success'], True) self.assertEqual(len(result['todos']), 3) for t in result['todos']: self.assertEqual(t['template'], todolist_template.id) self.assertEqual(t['section'], None) self.assertEqual(t['additional_data'], additional_data)
def test_create_todos_from_template_key_error(self, mock_request): # This converts `requests.post` into DRF's `APIClient.post` # To make it testable def post(url, *args, **kwargs): kw = kwargs.get('data', '') data = json.loads(kw) return_value = self.request_client.post(url, data, format='json') return_value.text = json.dumps(return_value.json()) return return_value TodoListTemplateFactory( slug=self.todolist_template_slug, name=self.todolist_template_name, description=self.todolist_template_description, todos={ 'items': [{ 'id': 1, 'description': 'todo parent', 'project': self.project.id, 'step': self.step.slug, 'items': [{ 'id': 2, 'project': self.project.id, 'step': self.step.slug, 'description': 'todo child', 'items': [] }] }] }, ) mock_request.post = post additional_data = {'some_additional_data': 'value'} result = create_todos_from_template(self.todolist_template_slug, self.project.id, None, additional_data) err_msg = ('An object with `template_slug`, `step_slug`,' ' and `project_id` attributes should be supplied') self.assertEqual(result['success'], False) self.assertEqual(len(result['errors']), 1) self.assertEqual(result['errors']['error'], err_msg)
def test_todolist_template_update(self): todolist_template = TodoListTemplateFactory() updated_description = 'updated description' todolist_template_detail_url = reverse( self.todolist_template_detail_url_name, kwargs={'pk': todolist_template.id}) resp = self.request_client.put(todolist_template_detail_url, json.dumps( self._todolist_template_data( todolist_template.slug, todolist_template.name, updated_description)), content_type='application/json') updated_todolist_template = TodoListTemplateSerializer( TodoListTemplate.objects.get(id=todolist_template.id)).data self.assertEqual(resp.status_code, 200) self._verify_todolist_template_content( updated_todolist_template, self._todolist_template_data(todolist_template.slug, todolist_template.name, updated_description))
class TodoListTemplatesImportExportTests(OrchestraTransactionTestCase): def setUp(self): super().setUp() setup_models(self) self.worker = Worker.objects.get(user__username='******') self.worker.user.is_staff = True self.worker.user.save() self.request_client.login(username=self.worker.user.username, password='******') self.full_todo_list_template = TodoListTemplateFactory( slug='full-template-slug', name='Full template name', description='Full template description', conditional_property_function={ 'path': 'orchestra.tests.test_todos' '._get_test_conditional_props' }, todos=TODO_TEMPLATE_NESTED_TODOS) self.empty_todo_list_template = TodoListTemplateFactory( slug='empty-template-slug', name='Empty template name', description='Empty template description', conditional_property_function={ 'path': 'orchestra.tests.test_todos' '._get_test_conditional_props' }, todos={}) self.import_url_name = ( 'orchestra:todos:import_todo_list_template_from_spreadsheet') self.template_admin_name = 'admin:orchestra_todolisttemplate_change' self.export_url_name = 'admin:orchestra_todolisttemplate_actions' @patch('orchestra.todos.import_export._upload_csv_to_google') def test_export_spreadsheet(self, mock_upload): mock_upload.return_value = 'https://redirect.com/the_spreadsheet' export_url = reverse(self.export_url_name, kwargs={ 'pk': self.full_todo_list_template.id, 'tool': 'export_spreadsheet' }) response = self.request_client.get(export_url) self.assertEqual(response.status_code, 302) self.assertEqual(response.url, 'https://redirect.com/the_spreadsheet') self.assertEqual(mock_upload.call_count, 1) title_prefix = (mock_upload.call_args[0][0] [:len(self.full_todo_list_template.name) + 2]) self.assertEqual(title_prefix, '{} -'.format(self.full_todo_list_template.name)) with open(mock_upload.call_args[0][1].name, 'r') as exported_file: lines = ''.join(exported_file.readlines()) # The JSON encoding of properties is nondeterministic in order. lines = lines.replace( '"[{""prop"": {""operator"": ""=="", ""value"": true}}]"', 'PROPREPLACED') lines = lines.replace( '"[{""prop"": {""value"": true, ""operator"": ""==""}}]"', 'PROPREPLACED') correct_text = TODO_TEMPLATE_GOOD_CSV_TEXT.replace( '"[{""prop"": {""value"": true, ""operator"": ""==""}}]"', 'PROPREPLACED') self.assertEqual(lines, correct_text) @patch('orchestra.todos.import_export.get_google_spreadsheet_as_csv') def test_import_spreadsheet(self, mock_get_spreadsheet): mock_get_spreadsheet.side_effect = self._fake_get_spreadsheet( TODO_TEMPLATE_GOOD_CSV_TEXT) import_url = reverse(self.import_url_name, kwargs={'pk': self.empty_todo_list_template.id}) admin_url = reverse( self.template_admin_name, kwargs={'object_id': self.empty_todo_list_template.id}) response = self.request_client.get(import_url) self.assertEqual(response.status_code, 200) self.assertTemplateUsed( response, 'orchestra/import_todo_list_template_from_spreadsheet.html') self.assertEqual(mock_get_spreadsheet.call_count, 0) # Before import, the empty_todo_list_template should have no # template to-dos. After import, it should have the same # to-dos as full_todo_list_template (the CSV we load from is # equivalent to full_todo_list_template's to-dos). self.empty_todo_list_template.refresh_from_db() self.assertEqual(self.empty_todo_list_template.todos, {}) self.assertEqual(TodoListTemplateImportRecord.objects.all().count(), 0) response = self.request_client.post( import_url, {'spreadsheet_url': 'https://the-spreadsheet.com/url'}) self.assertRedirects( response, admin_url, target_status_code=403) # User doesn't have model access. self.assertEqual(mock_get_spreadsheet.call_count, 1) self.assertEqual(mock_get_spreadsheet.call_args[0][0], 'https://the-spreadsheet.com/url') self.empty_todo_list_template.refresh_from_db() # Walk the to-do tree, moving IDs (which won't be equal, but # should be unique) into a set. Ensure the rest of the fields # are equivalent after setting missing `skip_id`/`remove_if` # to `[]`. def recursively_pop_ids(todos, id_set): id = todos.pop('id', None) if id is not None: id_set.add(id) todos.setdefault('remove_if', []) todos.setdefault('skip_if', []) for item in todos.get('items', []): recursively_pop_ids(item, id_set) empty_ids = set() full_ids = set() empty_todos = copy.deepcopy(self.empty_todo_list_template.todos) full_todos = copy.deepcopy(self.full_todo_list_template.todos) recursively_pop_ids(empty_todos, empty_ids) recursively_pop_ids(full_todos, full_ids) self.assertEqual(len(empty_ids), 7) self.assertEqual(len(empty_ids), len(full_ids)) self.assertEqual(empty_todos, full_todos) self.assertEqual(TodoListTemplateImportRecord.objects.all().count(), 1) import_record = TodoListTemplateImportRecord.objects.first() self.assertEqual(import_record.todo_list_template.id, self.empty_todo_list_template.id) self.assertEqual(import_record.importer.id, self.worker.user.id) self.assertEqual(import_record.import_url, 'https://the-spreadsheet.com/url') @patch('orchestra.todos.import_export.get_google_spreadsheet_as_csv') def test_import_spreadsheet_errors(self, mock_get_spreadsheet): import_url = reverse(self.import_url_name, kwargs={'pk': self.empty_todo_list_template.id}) mock_get_spreadsheet.side_effect = self._fake_get_spreadsheet( TODO_TEMPLATE_BAD_HEADER_CSV_TEXT) response = self.request_client.post( import_url, {'spreadsheet_url': 'https://the-spreadsheet.com/url'}) self.assertEqual(response.status_code, 200) # We didn't redirect. self.assertEqual(mock_get_spreadsheet.call_count, 1) self.assertRegex(response.content.decode('utf-8'), 'Error: Unexpected header:') mock_get_spreadsheet.reset_mock() mock_get_spreadsheet.side_effect = self._fake_get_spreadsheet( TODO_TEMPLATE_TWO_ENTRIES_CSV_TEXT) response = self.request_client.post( import_url, {'spreadsheet_url': 'https://the-spreadsheet.com/url'}) self.assertEqual(response.status_code, 200) # We didn't redirect. self.assertEqual(mock_get_spreadsheet.call_count, 1) self.assertRegex(response.content.decode('utf-8'), 'Error: More than one text entry in row 0: ') mock_get_spreadsheet.reset_mock() mock_get_spreadsheet.side_effect = self._fake_get_spreadsheet( TODO_TEMPLATE_INVALID_PARENT_CSV_TEXT) response = self.request_client.post( import_url, {'spreadsheet_url': 'https://the-spreadsheet.com/url'}) self.assertEqual(response.status_code, 200) # We didn't redirect. self.assertEqual(mock_get_spreadsheet.call_count, 1) self.assertRegex(response.content.decode('utf-8'), 'Error: Row 1 has skipped some columns in depth: ') mock_get_spreadsheet.reset_mock() # Nothing should have been created/updated since every import # failed. self.empty_todo_list_template.refresh_from_db() self.assertEqual(self.empty_todo_list_template.todos, {}) self.assertEqual(TodoListTemplateImportRecord.objects.all().count(), 0) def _fake_get_spreadsheet(self, csv_text): def fake_get_spreadsheet(spreadsheet_url, reader): return reader(StringIO(csv_text), dialect=csv.excel) return fake_get_spreadsheet
def test_conditional_skip_remove_todos_from_template(self): update_todos_from_todolist_template_url = \ reverse('orchestra:todos:update_todos_from_todolist_template') todolist_template = TodoListTemplateFactory( slug=self.todolist_template_slug, name=self.todolist_template_name, description=self.todolist_template_description, conditional_property_function={ 'path': 'orchestra.tests.test_todos' '._get_test_conditional_props' }, todos={ 'items': [{ 'id': 1, 'description': 'todo parent 1', 'project': self.project.id, 'items': [{ 'id': 2, 'description': 'todo child 1', 'project': self.project.id, 'items': [] }], 'remove_if': [{ 'prop1': { 'operator': '==', 'value': True } }] }, { 'id': 3, 'description': 'todo parent 2', 'project': self.project.id, 'items': [{ 'id': 4, 'description': 'todo child 2', 'project': self.project.id, 'items': [], 'skip_if': [{ 'prop2': { 'operator': '!=', 'value': True } }] }] }] }, ) resp = self.request_client.post( update_todos_from_todolist_template_url, { 'todolist_template': todolist_template.slug, 'project': self.project.id, 'step': self.step.slug }) self.assertEqual(resp.status_code, 200) todos = load_encoded_json(resp.content) expected_todos = [ _todo_data('todo child 2', False, template=todolist_template.id, parent_todo=todos[1]['id'], skipped_datetime=timezone.now(), project=self.project.id, step=self.step.slug), _todo_data('todo parent 2', False, template=todolist_template.id, parent_todo=todos[2]['id'], project=self.project.id, step=self.step.slug), _todo_data(self.todolist_template_name, False, template=todolist_template.id, project=self.project.id, step=self.step.slug), ] for todo, expected_todo in zip(todos, expected_todos): self._verify_todo_content(todo, expected_todo)