Ejemplo n.º 1
0
    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'
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
    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)
Ejemplo n.º 8
0
    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))
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
    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)