Add asana importer

remotes/origin/github-import
Jesús Espino 2017-01-18 09:40:37 +01:00 committed by David Barragán Merino
parent cca421b14d
commit 477d964770
9 changed files with 889 additions and 0 deletions

View File

@ -39,3 +39,4 @@ psd-tools==1.4
CairoSVG==2.0.1
cryptography==1.7.1
PyJWT==1.4.2
asana==0.6.2

View File

@ -567,6 +567,10 @@ GITHUB_API_CLIENT_SECRET = ""
TRELLO_API_KEY = ""
TRELLO_SECRET_KEY = ""
ASANA_APP_CALLBACK_URL = ""
ASANA_APP_ID = ""
ASANA_APP_SECRET = ""
JIRA_CONSUMER_KEY = ""
JIRA_CERT = ""
JIRA_PUB_CERT = ""

View File

@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Taiga Agile LLC <support@taiga.io>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.utils.translation import ugettext as _
from django.conf import settings
from taiga.base.api import viewsets
from taiga.base import response
from taiga.base import exceptions as exc
from taiga.base.decorators import list_route
from taiga.users.models import AuthData, User
from taiga.users.services import get_user_photo_url
from taiga.users.gravatar import get_user_gravatar_id
from taiga.projects.serializers import ProjectSerializer
from taiga.importers import permissions, exceptions
from .importer import AsanaImporter
from . import tasks
class AsanaImporterViewSet(viewsets.ViewSet):
permission_classes = (permissions.ImporterPermission,)
@list_route(methods=["POST"])
def list_users(self, request, *args, **kwargs):
self.check_permissions(request, "list_users", None)
token = request.DATA.get('token', None)
project_id = request.DATA.get('project', None)
if not project_id:
raise exc.WrongArguments(_("The project param is needed"))
importer = AsanaImporter(request.user, token)
try:
users = importer.list_users(project_id)
except exceptions.InvalidRequest:
raise exc.BadRequest(_('Invalid asana api request'))
except exceptions.FailedRequest:
raise exc.BadRequest(_('Failed to make the request to asana api'))
for user in users:
if user['detected_user']:
user['user'] = {
'id': user['detected_user'].id,
'full_name': user['detected_user'].get_full_name(),
'gravatar_id': get_user_gravatar_id(user['detected_user']),
'photo': get_user_photo_url(user['detected_user']),
}
del(user['detected_user'])
return response.Ok(users)
@list_route(methods=["POST"])
def list_projects(self, request, *args, **kwargs):
self.check_permissions(request, "list_projects", None)
token = request.DATA.get('token', None)
importer = AsanaImporter(request.user, token)
try:
projects = importer.list_projects()
except exceptions.InvalidRequest:
raise exc.BadRequest(_('Invalid asana api request'))
except exceptions.FailedRequest:
raise exc.BadRequest(_('Failed to make the request to asana api'))
return response.Ok(projects)
@list_route(methods=["POST"])
def import_project(self, request, *args, **kwargs):
self.check_permissions(request, "import_project", None)
token = request.DATA.get('token', None)
project_id = request.DATA.get('project', None)
if not project_id:
raise exc.WrongArguments(_("The project param is needed"))
options = {
"name": request.DATA.get('name', None),
"description": request.DATA.get('description', None),
"template": request.DATA.get('template', "scrum"),
"users_bindings": request.DATA.get("users_bindings", {}),
"keep_external_reference": request.DATA.get("keep_external_reference", False),
"is_private": request.DATA.get("is_private", False),
}
if settings.CELERY_ENABLED:
task = tasks.import_project.delay(request.user.id, token, project_id, options)
return response.Accepted({"task_id": task.id})
importer = AsanaImporter(request.user, token)
project = importer.import_project(project_id, options)
project_data = {
"slug": project.slug,
"my_permissions": ["view_us"],
"is_backlog_activated": project.is_backlog_activated,
"is_kanban_activated": project.is_kanban_activated,
}
return response.Ok(project_data)
@list_route(methods=["GET"])
def auth_url(self, request, *args, **kwargs):
self.check_permissions(request, "auth_url", None)
url = AsanaImporter.get_auth_url(settings.ASANA_APP_ID, settings.ASANA_APP_SECRET, settings.ASANA_APP_CALLBACK_URL)
return response.Ok({"url": url})
@list_route(methods=["POST"])
def authorize(self, request, *args, **kwargs):
self.check_permissions(request, "authorize", None)
code = request.DATA.get('code', None)
if code is None:
raise exc.BadRequest(_("Code param needed"))
try:
asana_token = AsanaImporter.get_access_token(code, settings.ASANA_APP_ID, settings.ASANA_APP_SECRET, settings.ASANA_APP_CALLBACK_URL)
except exceptions.InvalidRequest:
raise exc.BadRequest(_('Invalid asana api request'))
except exceptions.FailedRequest:
raise exc.BadRequest(_('Failed to make the request to asana api'))
return response.Ok({"token": asana_token})

View File

@ -0,0 +1,351 @@
import requests
import asana
import json
from django.core.files.base import ContentFile
from django.contrib.contenttypes.models import ContentType
from taiga.projects.models import Project, ProjectTemplate, Membership
from taiga.projects.userstories.models import UserStory
from taiga.projects.tasks.models import Task
from taiga.projects.attachments.models import Attachment
from taiga.projects.history.services import take_snapshot
from taiga.projects.history.models import HistoryEntry
from taiga.projects.custom_attributes.models import UserStoryCustomAttribute, TaskCustomAttribute
from taiga.users.models import User
from taiga.timeline.rebuilder import rebuild_timeline
from taiga.timeline.models import Timeline
from taiga.importers import exceptions
class AsanaClient(asana.Client):
def request(self, method, path, **options):
try:
return super().request(method, path, **options)
except asana.error.AsanaError:
raise exceptions.InvalidRequest()
except Exception as e:
raise exceptions.FailedRequest()
class AsanaImporter:
def __init__(self, user, token, import_closed_data=False):
self._import_closed_data = import_closed_data
self._user = user
self._client = AsanaClient.oauth(token=token)
def list_projects(self):
projects = []
for ws in self._client.workspaces.find_all():
for project in self._client.projects.find_all(workspace=ws['id']):
project = self._client.projects.find_by_id(project['id'])
projects.append({
"id": project['id'],
"name": "{}/{}".format(ws['name'], project['name']),
"description": project['notes'],
"is_private": True,
})
return projects
def list_users(self, project_id):
users = []
for ws in self._client.workspaces.find_all():
for user in self._client.users.find_by_workspace(ws['id'], fields=["id", "name", "email"]):
users.append({
"id": user["id"],
"full_name": user['name'],
"detected_user": self._get_user(user)
})
return users
def _get_user(self, user, default=None):
if not user:
return default
try:
return User.objects.get(email=user['email'])
except User.DoesNotExist:
pass
return default
def import_project(self, project_id, options):
project = self._client.projects.find_by_id(project_id)
taiga_project = self._import_project_data(project, options)
self._import_user_stories_data(taiga_project, project, options)
Timeline.objects.filter(project=taiga_project).delete()
rebuild_timeline(None, None, taiga_project.id)
return taiga_project
def _import_project_data(self, project, options):
users_bindings = options.get('users_bindings', {})
project_template = ProjectTemplate.objects.get(slug=options.get('template', 'scrum'))
project_template.us_statuses = []
project_template.us_statuses.append({
"name": "Open",
"slug": "open",
"is_closed": False,
"is_archived": False,
"color": "#ff8a84",
"wip_limit": None,
"order": 1,
})
project_template.us_statuses.append({
"name": "Closed",
"slug": "closed",
"is_closed": True,
"is_archived": False,
"color": "#669900",
"wip_limit": None,
"order": 2,
})
project_template.default_options["us_status"] = "Open"
project_template.task_statuses = []
project_template.task_statuses.append({
"name": "Open",
"slug": "open",
"is_closed": False,
"color": "#ff8a84",
"order": 1,
})
project_template.task_statuses.append({
"name": "Closed",
"slug": "closed",
"is_closed": True,
"color": "#669900",
"order": 2,
})
project_template.default_options["task_status"] = "Open"
project_template.roles.append({
"name": "Asana",
"slug": "asana",
"computable": False,
"permissions": project_template.roles[0]['permissions'],
"order": 70,
})
tags_colors = []
for tag in self._client.tags.find_by_workspace(project['workspace']['id'], fields=["name", "color"]):
name = tag['name'].lower()
color = tag['color']
tags_colors.append([name, color])
taiga_project = Project.objects.create(
name=options.get('name', None) or project['name'],
description=options.get('description', None) or project['notes'],
owner=self._user,
tags_colors=tags_colors,
creation_template=project_template,
is_private=options.get('is_private', False)
)
for user in self._client.users.find_by_workspace(project['workspace']['id']):
taiga_user = users_bindings.get(user['id'], None)
if taiga_user is None or taiga_user == self._user:
continue
Membership.objects.create(
user=taiga_user,
project=taiga_project,
role=taiga_project.get_roles().get(slug="asana"),
is_admin=False,
invited_by=self._user,
)
UserStoryCustomAttribute.objects.create(
name="Due date",
description="Due date",
type="date",
order=1,
project=taiga_project
)
TaskCustomAttribute.objects.create(
name="Due date",
description="Due date",
type="date",
order=1,
project=taiga_project
)
return taiga_project
def _import_user_stories_data(self, taiga_project, project, options):
users_bindings = options.get('users_bindings', {})
tasks = self._client.tasks.find_by_project(
project['id'],
fields=["parent", "tags", "name", "notes", "tags.name",
"completed", "followers", "modified_at", "created_at",
"project", "due_on"]
)
due_date_field = taiga_project.userstorycustomattributes.first()
for task in tasks:
if task['parent']:
continue
tags = []
for tag in task['tags']:
tags.append(tag['name'].lower())
assigned_to = users_bindings.get(task.get('assignee', {}).get('id', None)) or None
external_reference = None
if options.get('keep_external_reference', False):
external_url = "https://app.asana.com/0/{}/{}".format(
project['id'],
task['id'],
)
external_reference = ["asana", external_url]
us = UserStory.objects.create(
project=taiga_project,
owner=self._user,
assigned_to=assigned_to,
status=taiga_project.us_statuses.get(slug="closed" if task['completed'] else "open"),
kanban_order=task['id'],
sprint_order=task['id'],
backlog_order=task['id'],
subject=task['name'],
description=task.get('notes', ""),
tags=tags,
external_reference=external_reference
)
if task['due_on']:
us.custom_attributes_values.attributes_values = {due_date_field.id: task['due_on']}
us.custom_attributes_values.save()
for follower in task['followers']:
follower_user = users_bindings.get(follower['id'], None)
if follower_user is not None:
us.add_watcher(follower_user)
UserStory.objects.filter(id=us.id).update(
modified_date=task['modified_at'],
created_date=task['created_at']
)
subtasks = self._client.tasks.subtasks(
task['id'],
fields=["parent", "tags", "name", "notes", "tags.name",
"completed", "followers", "modified_at", "created_at",
"due_on"]
)
for subtask in subtasks:
self._import_task_data(taiga_project, us, project, subtask, options)
take_snapshot(us, comment="", user=None, delete=False)
self._import_history(us, task, options)
self._import_attachments(us, task, options)
def _import_task_data(self, taiga_project, us, assana_project, task, options):
users_bindings = options.get('users_bindings', {})
tags = []
for tag in task['tags']:
tags.append(tag['name'].lower())
due_date_field = taiga_project.taskcustomattributes.first()
assigned_to = users_bindings.get(task.get('assignee', {}).get('id', None)) or None
external_reference = None
if options.get('keep_external_reference', False):
external_url = "https://app.asana.com/0/{}/{}".format(
assana_project['id'],
task['id'],
)
external_reference = ["asana", external_url]
taiga_task = Task.objects.create(
project=taiga_project,
user_story=us,
owner=self._user,
assigned_to=assigned_to,
status=taiga_project.task_statuses.get(slug="closed" if task['completed'] else "open"),
us_order=task['id'],
taskboard_order=task['id'],
subject=task['name'],
description=task.get('notes', ""),
tags=tags,
external_reference=external_reference
)
if task['due_on']:
taiga_task.custom_attributes_values.attributes_values = {due_date_field.id: task['due_on']}
taiga_task.custom_attributes_values.save()
for follower in task['followers']:
follower_user = users_bindings.get(follower['id'], None)
if follower_user is not None:
taiga_task.add_watcher(follower_user)
Task.objects.filter(id=taiga_task.id).update(
modified_date=task['modified_at'],
created_date=task['created_at']
)
subtasks = self._client.tasks.subtasks(
task['id'],
fields=["parent", "tags", "name", "notes", "tags.name",
"completed", "followers", "modified_at", "created_at",
"due_on"]
)
for subtask in subtasks:
self._import_task_data(taiga_project, us, assana_project, subtask, options)
take_snapshot(taiga_task, comment="", user=None, delete=False)
self._import_history(taiga_task, task, options)
self._import_attachments(taiga_task, task, options)
def _import_history(self, obj, task, options):
users_bindings = options.get('users_bindings', {})
stories = self._client.stories.find_by_task(task['id'])
for story in stories:
if story['type'] == "comment":
snapshot = take_snapshot(
obj,
comment=story['text'],
user=users_bindings.get(story['created_by']['id'], User(full_name=story['created_by']['name'])),
delete=False
)
HistoryEntry.objects.filter(id=snapshot.id).update(created_at=story['created_at'])
def _import_attachments(self, obj, task, options):
attachments = self._client.attachments.find_by_task(
task['id'],
fields=['name', 'download_url', 'created_at']
)
for attachment in attachments:
data = requests.get(attachment['download_url'])
att = Attachment(
owner=self._user,
project=obj.project,
content_type=ContentType.objects.get_for_model(obj),
object_id=obj.id,
name=attachment['name'],
size=len(data.content),
created_date=attachment['created_at'],
is_deprecated=False,
)
att.attached_file.save(attachment['name'], ContentFile(data.content), save=True)
@classmethod
def get_auth_url(cls, client_id, client_secret, callback_url=None):
client = AsanaClient.oauth(
client_id=client_id,
client_secret=client_secret,
redirect_uri=callback_url
)
(url, state) = client.session.authorization_url()
return url
@classmethod
def get_access_token(cls, code, client_id, client_secret, callback_url=None):
client = AsanaClient.oauth(
client_id=client_id,
client_secret=client_secret,
redirect_uri=callback_url
)
return client.session.fetch_token(code=code)

View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import sys
from django.utils.translation import ugettext as _
from taiga.base.mails import mail_builder
from taiga.users.models import User
from taiga.celery import app
from .importer import AsanaImporter
logger = logging.getLogger('taiga.importers.asana')
@app.task(bind=True)
def import_project(self, user_id, token, project_id, options):
user = User.object.get(id=user_id)
importer = AsanaImporter(user, token)
try:
project = importer.import_project(project_id, options)
except Exception as e:
# Error
ctx = {
"user": user,
"error_subject": _("Error importing asana project"),
"error_message": _("Error importing asana project"),
"project": project_id,
"exception": e
}
email = mail_builder.asana_import_error(admin, ctx)
email.send()
logger.error('Error importing asana project %s (by %s)', project_id, user, exc_info=sys.exc_info())
else:
ctx = {
"project": project,
"user": user,
}
email = mail_builder.asana_import_success(user, ctx)
email.send()

View File

@ -1,3 +1,6 @@
class InvalidRequest(Exception):
pass
class InvalidAuthResult(Exception):
pass

View File

@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.core.management.base import BaseCommand
from django.conf import settings
from django.db.models import Q
from taiga.importers.asana.importer import AsanaImporter
from taiga.users.models import User, AuthData
from taiga.projects.services import projects as service
import unittest.mock
import timeit
import json
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('--token', dest="token", type=str,
help='Auth token')
parser.add_argument('--project-id', dest="project_id", type=str,
help='Project ID or full name (ex: taigaio/taiga-back)')
parser.add_argument('--template', dest='template', default="kanban",
help='template to use: scrum or kanban (default kanban)')
parser.add_argument('--type', dest='type', default="user_stories",
help='type of object to use: user_stories or issues (default user_stories)')
parser.add_argument('--ask-for-users', dest='ask_for_users', const=True,
action="store_const", default=False,
help='Import closed data')
parser.add_argument('--keep-external-reference', dest='keep_external_reference', const=True,
action="store_const", default=False,
help='Store external reference of imported data')
def handle(self, *args, **options):
admin = User.objects.get(username="admin")
if options.get('token', None):
token = json.loads(options.get('token'))
else:
url = AsanaImporter.get_auth_url(settings.ASANA_APP_ID, settings.ASANA_APP_SECRET, settings.ASANA_APP_CALLBACK_URL)
print("Go to here and come with your code (in the redirected url): {}".format(url))
code = input("Code: ")
access_data = AsanaImporter.get_access_token(code, settings.ASANA_APP_ID, settings.ASANA_APP_SECRET, settings.ASANA_APP_CALLBACK_URL)
token = access_data
importer = AsanaImporter(admin, token)
if options.get('project_id', None):
project_id = options.get('project_id')
else:
print("Select the project to import:")
for project in importer.list_projects():
print("- {}: {}".format(project['id'], project['name']))
project_id = input("Project id: ")
users_bindings = {}
if options.get('ask_for_users', None):
print("Add the username or email for next asana users:")
for user in importer.list_users(project_id):
while True:
if user['detected_user'] is not None:
print("User automatically detected: {} as {}".format(user['full_name'], user['detected_user']))
users_bindings[user['id']] = user['detected_user']
break
if not options.get('ask_for_users', False):
break
username_or_email = input("{}: ".format(user['full_name'] or user['username']))
if username_or_email == "":
break
try:
users_bindings[user['id']] = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email))
break
except User.DoesNotExist:
print("ERROR: Invalid username or email")
options = {
"template": options.get('template'),
"type": options.get('type'),
"users_bindings": users_bindings,
"keep_external_reference": options.get('keep_external_reference')
}
importer.import_project(project_id, options)

View File

@ -287,10 +287,12 @@ router.register(r"application-tokens", ApplicationToken, base_name="application-
from taiga.importers.trello.api import TrelloImporterViewSet
from taiga.importers.jira.api import JiraImporterViewSet
from taiga.importers.github.api import GithubImporterViewSet
from taiga.importers.asana.api import AsanaImporterViewSet
router.register(r"importers/trello", TrelloImporterViewSet, base_name="importers-trello")
router.register(r"importers/jira", JiraImporterViewSet, base_name="importers-jira")
router.register(r"importers/github", GithubImporterViewSet, base_name="importers-github")
router.register(r"importers/asana", AsanaImporterViewSet, base_name="importers-asana")
# Stats

View File

@ -0,0 +1,236 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
import json
from unittest import mock
from django.core.urlresolvers import reverse
from .. import factories as f
from taiga.importers import exceptions
from taiga.base.utils import json
from taiga.base import exceptions as exc
pytestmark = pytest.mark.django_db
def test_auth_url(client, settings):
user = f.UserFactory.create()
client.login(user)
settings.ASANA_APP_CALLBACK_URL = "http://testserver/url"
settings.ASANA_APP_ID = "test-id"
settings.ASANA_APP_SECRET = "test-secret"
url = reverse("importers-asana-auth-url")
with mock.patch('taiga.importers.asana.api.AsanaImporter') as AsanaImporterMock:
AsanaImporterMock.get_auth_url.return_value = "https://auth_url"
response = client.get(url, content_type="application/json")
assert AsanaImporterMock.get_auth_url.calledWith(settings.ASANA_APP_ID, settings.ASANA_APP_SECRET, settings.ASANA_APP_CALLBACK_URL)
assert response.status_code == 200
assert 'url' in response.data
assert response.data['url'] == "https://auth_url"
def test_authorize(client, settings):
user = f.UserFactory.create()
client.login(user)
authorize_url = reverse("importers-asana-authorize")
with mock.patch('taiga.importers.asana.api.AsanaImporter') as AsanaImporterMock:
AsanaImporterMock.get_access_token.return_value = "token"
response = client.post(authorize_url, content_type="application/json", data=json.dumps({"code": "code"}))
assert AsanaImporterMock.get_access_token.calledWith(settings.ASANA_APP_ID, settings.ASANA_APP_SECRET, "code")
assert response.status_code == 200
assert 'token' in response.data
assert response.data['token'] == "token"
def test_authorize_without_code(client):
user = f.UserFactory.create()
client.login(user)
authorize_url = reverse("importers-asana-authorize")
response = client.post(authorize_url, content_type="application/json", data=json.dumps({}))
assert response.status_code == 400
assert 'token' not in response.data
assert '_error_message' in response.data
assert response.data['_error_message'] == "Code param needed"
def test_authorize_with_bad_verify(client, settings):
user = f.UserFactory.create()
client.login(user)
authorize_url = reverse("importers-asana-authorize")
with mock.patch('taiga.importers.asana.api.AsanaImporter') as AsanaImporterMock:
AsanaImporterMock.get_access_token.side_effect = exceptions.InvalidRequest()
response = client.post(authorize_url, content_type="application/json", data=json.dumps({"code": "bad"}))
assert AsanaImporterMock.get_access_token.calledWith(settings.ASANA_APP_ID, settings.ASANA_APP_SECRET, "bad")
assert response.status_code == 400
assert 'token' not in response.data
assert '_error_message' in response.data
assert response.data['_error_message'] == "Invalid asana api request"
def test_import_asana_list_users(client):
user = f.UserFactory.create()
client.login(user)
url = reverse("importers-asana-list-users")
with mock.patch('taiga.importers.asana.api.AsanaImporter') as AsanaImporterMock:
instance = mock.Mock()
instance.list_users.return_value = [
{"id": 1, "username": "user1", "full_name": "user1", "detected_user": None},
{"id": 2, "username": "user2", "full_name": "user2", "detected_user": None}
]
AsanaImporterMock.return_value = instance
response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1}))
assert response.status_code == 200
assert response.data[0]["id"] == 1
assert response.data[1]["id"] == 2
def test_import_asana_list_users_without_project(client):
user = f.UserFactory.create()
client.login(user)
url = reverse("importers-asana-list-users")
with mock.patch('taiga.importers.asana.api.AsanaImporter') as AsanaImporterMock:
instance = mock.Mock()
instance.list_users.return_value = [
{"id": 1, "username": "user1", "full_name": "user1", "detected_user": None},
{"id": 2, "username": "user2", "full_name": "user2", "detected_user": None}
]
AsanaImporterMock.return_value = instance
response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"}))
assert response.status_code == 400
def test_import_asana_list_users_with_problem_on_request(client):
user = f.UserFactory.create()
client.login(user)
url = reverse("importers-asana-list-users")
with mock.patch('taiga.importers.asana.importer.AsanaClient') as AsanaClientMock:
instance = mock.Mock()
instance.workspaces.find_all.side_effect = exceptions.InvalidRequest()
AsanaClientMock.oauth.return_value = instance
response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1}))
assert response.status_code == 400
def test_import_asana_list_projects(client):
user = f.UserFactory.create()
client.login(user)
url = reverse("importers-asana-list-projects")
with mock.patch('taiga.importers.asana.api.AsanaImporter') as AsanaImporterMock:
instance = mock.Mock()
instance.list_projects.return_value = ["project1", "project2"]
AsanaImporterMock.return_value = instance
response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"}))
assert response.status_code == 200
assert response.data[0] == "project1"
assert response.data[1] == "project2"
def test_import_asana_list_projects_with_problem_on_request(client):
user = f.UserFactory.create()
client.login(user)
url = reverse("importers-asana-list-projects")
with mock.patch('taiga.importers.asana.importer.AsanaClient') as AsanaClientMock:
instance = mock.Mock()
instance.workspaces.find_all.side_effect = exc.WrongArguments("Invalid Request")
AsanaClientMock.oauth.return_value = instance
response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"}))
assert response.status_code == 400
def test_import_asana_project_without_project_id(client, settings):
settings.CELERY_ENABLED = True
user = f.UserFactory.create()
client.login(user)
url = reverse("importers-asana-import-project")
with mock.patch('taiga.importers.asana.tasks.AsanaImporter') as AsanaImporterMock:
response = client.post(url, content_type="application/json", data=json.dumps({"token": "token"}))
assert response.status_code == 400
def test_import_asana_project_with_celery_enabled(client, settings):
settings.CELERY_ENABLED = True
user = f.UserFactory.create()
project = f.ProjectFactory.create(slug="async-imported-project")
client.login(user)
url = reverse("importers-asana-import-project")
with mock.patch('taiga.importers.asana.tasks.AsanaImporter') as AsanaImporterMock:
instance = mock.Mock()
instance.import_project.return_value = project
AsanaImporterMock.return_value = instance
response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1}))
assert response.status_code == 202
assert "task_id" in response.data
def test_import_asana_project_with_celery_disabled(client, settings):
settings.CELERY_ENABLED = False
user = f.UserFactory.create()
project = f.ProjectFactory.create(slug="imported-project")
client.login(user)
url = reverse("importers-asana-import-project")
with mock.patch('taiga.importers.asana.api.AsanaImporter') as AsanaImporterMock:
instance = mock.Mock()
instance.import_project.return_value = project
AsanaImporterMock.return_value = instance
response = client.post(url, content_type="application/json", data=json.dumps({"token": "token", "project": 1}))
assert response.status_code == 200
assert "slug" in response.data
assert response.data['slug'] == "imported-project"