diff --git a/CHANGELOG.md b/CHANGELOG.md
index be370f50..b2890f4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,11 @@
- Gzip export/import support.
- Export performance improvements.
- Add filter by email domain registration and invitation by setting.
+- Third party integrations:
+ - Included gogs as builtin integration.
+ - Improve messages generated on webhooks input.
+ - Add mentions support in commit messages.
+ - Cleanup hooks code.
### Misc
- [API] Improve performance of some calls over list.
diff --git a/settings/common.py b/settings/common.py
index 66fbb67f..333d310a 100644
--- a/settings/common.py
+++ b/settings/common.py
@@ -313,6 +313,7 @@ INSTALLED_APPS = [
"taiga.hooks.github",
"taiga.hooks.gitlab",
"taiga.hooks.bitbucket",
+ "taiga.hooks.gogs",
"taiga.webhooks",
"djmail",
@@ -506,6 +507,7 @@ PROJECT_MODULES_CONFIGURATORS = {
"github": "taiga.hooks.github.services.get_or_generate_config",
"gitlab": "taiga.hooks.gitlab.services.get_or_generate_config",
"bitbucket": "taiga.hooks.bitbucket.services.get_or_generate_config",
+ "gogs": "taiga.hooks.gogs.services.get_or_generate_config",
}
BITBUCKET_VALID_ORIGIN_IPS = ["131.103.20.165", "131.103.20.166", "104.192.143.192/28", "104.192.143.208/28"]
diff --git a/taiga/hooks/bitbucket/api.py b/taiga/hooks/bitbucket/api.py
index 24fc478c..07e2829b 100644
--- a/taiga/hooks/bitbucket/api.py
+++ b/taiga/hooks/bitbucket/api.py
@@ -72,13 +72,5 @@ class BitBucketViewSet(BaseWebhookApiViewSet):
return project_secret == secret_key
- def _get_project(self, request):
- project_id = request.GET.get("project", None)
- try:
- project = Project.objects.get(id=project_id)
- return project
- except Project.DoesNotExist:
- return None
-
def _get_event_name(self, request):
return request.META.get('HTTP_X_EVENT_KEY', None)
diff --git a/taiga/hooks/bitbucket/event_hooks.py b/taiga/hooks/bitbucket/event_hooks.py
index 8737aaa7..67ffc3fd 100644
--- a/taiga/hooks/bitbucket/event_hooks.py
+++ b/taiga/hooks/bitbucket/event_hooks.py
@@ -18,181 +18,67 @@
import re
-from django.utils.translation import ugettext as _
-
-from taiga.base import exceptions as exc
-from taiga.projects.models import IssueStatus, TaskStatus, UserStoryStatus
-from taiga.projects.issues.models import Issue
-from taiga.projects.tasks.models import Task
-from taiga.projects.userstories.models import UserStory
-from taiga.projects.history.services import take_snapshot
-from taiga.projects.notifications.services import send_notifications
-from taiga.hooks.event_hooks import BaseEventHook
-from taiga.hooks.exceptions import ActionSyntaxException
-from taiga.base.utils import json
-
-from .services import get_bitbucket_user
+from taiga.hooks.event_hooks import BaseNewIssueEventHook, BaseIssueCommentEventHook, BasePushEventHook
-class PushEventHook(BaseEventHook):
- def process_event(self):
- if self.payload is None:
- return
+class BaseBitBucketEventHook():
+ platform = "BitBucket"
+ platform_slug = "bitbucket"
+ def replace_bitbucket_references(self, project_url, wiki_text):
+ if wiki_text is None:
+ wiki_text = ""
+
+ template = "\g<1>[BitBucket#\g<2>]({}/issues/\g<2>)\g<3>".format(project_url)
+ return re.sub(r"(\s|^)#(\d+)(\s|$)", template, wiki_text, 0, re.M)
+
+
+class IssuesEventHook(BaseBitBucketEventHook, BaseNewIssueEventHook):
+ def get_data(self):
+ description = self.payload.get('issue', {}).get('content', {}).get('raw', '')
+ project_url = self.payload.get('repository', {}).get('links', {}).get('html', {}).get('href', None)
+ return {
+ "number": self.payload.get('issue', {}).get('id', None),
+ "subject": self.payload.get('issue', {}).get('title', None),
+ "url": self.payload.get('issue', {}).get('links', {}).get('html', {}).get('href', None),
+ "user_id": self.payload.get('actor', {}).get('uuid', None),
+ "user_name": self.payload.get('actor', {}).get('username', None),
+ "user_url": self.payload.get('actor', {}).get('links', {}).get('html', {}).get('href'),
+ "description": self.replace_bitbucket_references(project_url, description),
+ }
+
+
+class IssueCommentEventHook(BaseBitBucketEventHook, BaseIssueCommentEventHook):
+ def get_data(self):
+ comment_message = self.payload.get('comment', {}).get('content', {}).get('raw', '')
+ project_url = self.payload.get('repository', {}).get('links', {}).get('html', {}).get('href', None)
+ issue_url = self.payload.get('issue', {}).get('links', {}).get('html', {}).get('href', None)
+ comment_id = self.payload.get('comment', {}).get('id', None)
+ comment_url = "{}#comment-{}".format(issue_url, comment_id)
+ return {
+ "number": self.payload.get('issue', {}).get('id', None),
+ 'url': issue_url,
+ 'user_id': self.payload.get('actor', {}).get('uuid', None),
+ 'user_name': self.payload.get('actor', {}).get('username', None),
+ 'user_url': self.payload.get('actor', {}).get('links', {}).get('html', {}).get('href'),
+ 'comment_url': comment_url,
+ 'comment_message': self.replace_bitbucket_references(project_url, comment_message)
+ }
+
+
+class PushEventHook(BaseBitBucketEventHook, BasePushEventHook):
+ def get_data(self):
+ result = []
changes = self.payload.get("push", {}).get('changes', [])
for change in filter(None, changes):
- commits = change.get("commits", [])
- if not commits:
- continue
-
- for commit in commits:
- message = commit.get("message", None)
- if not message:
- continue
-
- self._process_message(message, None)
-
- def _process_message(self, message, bitbucket_user):
- """
- The message we will be looking for seems like
- TG-XX #yyyyyy
- Where:
- XX: is the ref for us, issue or task
- yyyyyy: is the status slug we are setting
- """
- if message is None:
- return
-
- p = re.compile("tg-(\d+) +#([-\w]+)")
- for m in p.finditer(message.lower()):
- ref = m.group(1)
- status_slug = m.group(2)
- self._change_status(ref, status_slug, bitbucket_user)
-
- def _change_status(self, ref, status_slug, bitbucket_user):
- if Issue.objects.filter(project=self.project, ref=ref).exists():
- modelClass = Issue
- statusClass = IssueStatus
- elif Task.objects.filter(project=self.project, ref=ref).exists():
- modelClass = Task
- statusClass = TaskStatus
- elif UserStory.objects.filter(project=self.project, ref=ref).exists():
- modelClass = UserStory
- statusClass = UserStoryStatus
- else:
- raise ActionSyntaxException(_("The referenced element doesn't exist"))
-
- element = modelClass.objects.get(project=self.project, ref=ref)
-
- try:
- status = statusClass.objects.get(project=self.project, slug=status_slug)
- except statusClass.DoesNotExist:
- raise ActionSyntaxException(_("The status doesn't exist"))
-
- element.status = status
- element.save()
-
- snapshot = take_snapshot(element,
- comment=_("Status changed from BitBucket commit"),
- user=get_bitbucket_user(bitbucket_user))
- send_notifications(element, history=snapshot)
-
-
-def replace_bitbucket_references(project_url, wiki_text):
- template = "\g<1>[BitBucket#\g<2>]({}/issues/\g<2>)\g<3>".format(project_url)
- return re.sub(r"(\s|^)#(\d+)(\s|$)", template, wiki_text, 0, re.M)
-
-
-class IssuesEventHook(BaseEventHook):
- def process_event(self):
- number = self.payload.get('issue', {}).get('id', None)
- subject = self.payload.get('issue', {}).get('title', None)
-
- bitbucket_url = self.payload.get('issue', {}).get('links', {}).get('html', {}).get('href', None)
-
- bitbucket_user_id = self.payload.get('actor', {}).get('user', {}).get('uuid', None)
- bitbucket_user_name = self.payload.get('actor', {}).get('user', {}).get('username', None)
- bitbucket_user_url = self.payload.get('actor', {}).get('user', {}).get('links', {}).get('html', {}).get('href')
-
- project_url = self.payload.get('repository', {}).get('links', {}).get('html', {}).get('href', None)
-
- description = self.payload.get('issue', {}).get('content', {}).get('raw', '')
- description = replace_bitbucket_references(project_url, description)
-
- user = get_bitbucket_user(bitbucket_user_id)
-
- if not all([subject, bitbucket_url, project_url]):
- raise ActionSyntaxException(_("Invalid issue information"))
-
- issue = Issue.objects.create(
- project=self.project,
- subject=subject,
- description=description,
- status=self.project.default_issue_status,
- type=self.project.default_issue_type,
- severity=self.project.default_severity,
- priority=self.project.default_priority,
- external_reference=['bitbucket', bitbucket_url],
- owner=user
- )
- take_snapshot(issue, user=user)
-
- if number and subject and bitbucket_user_name and bitbucket_user_url:
- comment = _("Issue created by [@{bitbucket_user_name}]({bitbucket_user_url} "
- "\"See @{bitbucket_user_name}'s BitBucket profile\") "
- "from BitBucket.\nOrigin BitBucket issue: [bb#{number} - {subject}]({bitbucket_url} "
- "\"Go to 'bb#{number} - {subject}'\"):\n\n"
- "{description}").format(bitbucket_user_name=bitbucket_user_name,
- bitbucket_user_url=bitbucket_user_url,
- number=number,
- subject=subject,
- bitbucket_url=bitbucket_url,
- description=description)
- else:
- comment = _("Issue created from BitBucket.")
-
- snapshot = take_snapshot(issue, comment=comment, user=user)
- send_notifications(issue, history=snapshot)
-
-
-class IssueCommentEventHook(BaseEventHook):
- def process_event(self):
- number = self.payload.get('issue', {}).get('id', None)
- subject = self.payload.get('issue', {}).get('title', None)
-
- bitbucket_url = self.payload.get('issue', {}).get('links', {}).get('html', {}).get('href', None)
- bitbucket_user_id = self.payload.get('actor', {}).get('user', {}).get('uuid', None)
- bitbucket_user_name = self.payload.get('actor', {}).get('user', {}).get('username', None)
- bitbucket_user_url = self.payload.get('actor', {}).get('user', {}).get('links', {}).get('html', {}).get('href')
-
- project_url = self.payload.get('repository', {}).get('links', {}).get('html', {}).get('href', None)
-
- comment_message = self.payload.get('comment', {}).get('content', {}).get('raw', '')
- comment_message = replace_bitbucket_references(project_url, comment_message)
-
- user = get_bitbucket_user(bitbucket_user_id)
-
- if not all([comment_message, bitbucket_url, project_url]):
- raise ActionSyntaxException(_("Invalid issue comment information"))
-
- issues = Issue.objects.filter(external_reference=["bitbucket", bitbucket_url])
- tasks = Task.objects.filter(external_reference=["bitbucket", bitbucket_url])
- uss = UserStory.objects.filter(external_reference=["bitbucket", bitbucket_url])
-
- for item in list(issues) + list(tasks) + list(uss):
- if number and subject and bitbucket_user_name and bitbucket_user_url:
- comment = _("Comment by [@{bitbucket_user_name}]({bitbucket_user_url} "
- "\"See @{bitbucket_user_name}'s BitBucket profile\") "
- "from BitBucket.\nOrigin BitBucket issue: [bb#{number} - {subject}]({bitbucket_url} "
- "\"Go to 'bb#{number} - {subject}'\")\n\n"
- "{message}").format(bitbucket_user_name=bitbucket_user_name,
- bitbucket_user_url=bitbucket_user_url,
- number=number,
- subject=subject,
- bitbucket_url=bitbucket_url,
- message=comment_message)
- else:
- comment = _("Comment From BitBucket:\n\n{message}").format(message=comment_message)
-
- snapshot = take_snapshot(item, comment=comment, user=user)
- send_notifications(item, history=snapshot)
+ for commit in change.get("commits", []):
+ message = commit.get("message")
+ result.append({
+ 'user_id': commit.get('author', {}).get('user', {}).get('uuid', None),
+ "user_name": commit.get('author', {}).get('user', {}).get('username', None),
+ "user_url": commit.get('author', {}).get('user', {}).get('links', {}).get('html', {}).get('href'),
+ "commit_id": commit.get("hash", None),
+ "commit_url": commit.get("links", {}).get('html', {}).get('href'),
+ "commit_message": message.strip(),
+ })
+ return result
diff --git a/taiga/hooks/event_hooks.py b/taiga/hooks/event_hooks.py
index f4f6d2e8..85ac892f 100644
--- a/taiga/hooks/event_hooks.py
+++ b/taiga/hooks/event_hooks.py
@@ -16,11 +16,247 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+import re
+
+from django.utils.translation import ugettext as _
+from django.contrib.auth import get_user_model
+from taiga.projects.models import IssueStatus, TaskStatus, UserStoryStatus
+from taiga.projects.issues.models import Issue
+from taiga.projects.tasks.models import Task
+from taiga.projects.userstories.models import UserStory
+from taiga.projects.history.services import take_snapshot
+from taiga.projects.notifications.services import send_notifications
+from taiga.hooks.exceptions import ActionSyntaxException
+from taiga.users.models import AuthData
+
class BaseEventHook:
+ platform = "Unknown"
+ platform_slug = "unknown"
+
def __init__(self, project, payload):
self.project = project
self.payload = payload
+ def ignore(self):
+ return False
+
+ def get_user(self, user_id, platform):
+ user = None
+
+ if user_id:
+ try:
+ user = AuthData.objects.get(key=platform, value=user_id).user
+ except AuthData.DoesNotExist:
+ pass
+
+ if user is None:
+ user = get_user_model().objects.get(is_system=True, username__startswith=platform)
+
+ return user
+
+
+class BaseIssueCommentEventHook(BaseEventHook):
+ def get_data(self):
+ raise NotImplementedError
+
+ def generate_issue_comment_message(self, **kwargs):
+ _issue_comment_message = _(
+ "[@{user_name}]({user_url} "
+ "\"See @{user_name}'s {platform} profile\") "
+ "says in [{platform}#{number}]({comment_url} \"Go to comment\"):\n\n"
+ "\"{comment_message}\""
+ )
+ _simple_issue_comment_message = _("Comment From {platform}:\n\n> {comment_message}")
+ try:
+ return _issue_comment_message.format(platform=self.platform, **kwargs)
+ except Exception:
+ return _simple_issue_comment_message.format(platform=self.platform, message=kwargs.get('comment_message'))
+
def process_event(self):
- raise NotImplementedError("process_event must be overwritten")
+ if self.ignore():
+ return
+
+ data = self.get_data()
+
+ if not all([data['comment_message'], data['url']]):
+ raise ActionSyntaxException(_("Invalid issue comment information"))
+
+ comment = self.generate_issue_comment_message(**data)
+
+ issues = Issue.objects.filter(external_reference=[self.platform_slug, data['url']])
+ tasks = Task.objects.filter(external_reference=[self.platform_slug, data['url']])
+ uss = UserStory.objects.filter(external_reference=[self.platform_slug, data['url']])
+
+ for item in list(issues) + list(tasks) + list(uss):
+ snapshot = take_snapshot(item, comment=comment, user=self.get_user(data['user_id'], self.platform_slug))
+ send_notifications(item, history=snapshot)
+
+
+class BaseNewIssueEventHook(BaseEventHook):
+ def get_data(self):
+ raise NotImplementedError
+
+ def generate_new_issue_comment(self, **kwargs):
+ _new_issue_message = _(
+ "Issue created by [@{user_name}]({user_url} "
+ "\"See @{user_name}'s {platform} profile\") "
+ "from [{platform}#{number}]({url} \"Go to issue\")."
+ )
+ _simple_new_issue_message = _("Issue created from {platform}.")
+ try:
+ return _new_issue_message.format(platform=self.platform, **kwargs)
+ except Exception:
+ return _simple_new_issue_message.format(platform=self.platform)
+
+ def process_event(self):
+ if self.ignore():
+ return
+
+ data = self.get_data()
+
+ if not all([data['subject'], data['url']]):
+ raise ActionSyntaxException(_("Invalid issue information"))
+
+ user = self.get_user(data['user_id'], self.platform_slug)
+
+ issue = Issue.objects.create(
+ project=self.project,
+ subject=data['subject'],
+ description=data['description'],
+ status=self.project.default_issue_status,
+ type=self.project.default_issue_type,
+ severity=self.project.default_severity,
+ priority=self.project.default_priority,
+ external_reference=[self.platform_slug, data['url']],
+ owner=user
+ )
+ take_snapshot(issue, user=user)
+
+ comment = self.generate_new_issue_comment(**data)
+
+ snapshot = take_snapshot(issue, comment=comment, user=user)
+ send_notifications(issue, history=snapshot)
+
+
+class BasePushEventHook(BaseEventHook):
+ def get_data(self):
+ raise NotImplementedError
+
+ def generate_status_change_comment(self, **kwargs):
+ if kwargs.get('user_url', None) is None:
+ user_text = kwargs.get('user_name', _('unknown user'))
+ else:
+ user_text = "[@{user_name}]({user_url} \"See @{user_name}'s {platform} profile\")".format(
+ platform=self.platform,
+ **kwargs
+ )
+ _status_change_message = _(
+ "{user_text} changed the status from "
+ "[{platform} commit]({commit_url} \"See commit '{commit_id} - {commit_message}'\")\n\n"
+ " - Status: **{src_status}** → **{dst_status}**"
+ )
+ _simple_status_change_message = _(
+ "Changed status from {platform} commit.\n\n"
+ " - Status: **{src_status}** → **{dst_status}**"
+ )
+ try:
+ return _status_change_message.format(platform=self.platform, user_text=user_text, **kwargs)
+ except Exception:
+ return _simple_status_change_message.format(platform=self.platform)
+
+ def generate_commit_reference_comment(self, **kwargs):
+ if kwargs.get('user_url', None) is None:
+ user_text = kwargs.get('user_name', _('unknown user'))
+ else:
+ user_text = "[@{user_name}]({user_url} \"See @{user_name}'s {platform} profile\")".format(
+ platform=self.platform,
+ **kwargs
+ )
+
+ _status_change_message = _(
+ "This {type_name} has been mentioned by {user_text} "
+ "in the [{platform} commit]({commit_url} \"See commit '{commit_id} - {commit_message}'\") "
+ "\"{commit_message}\""
+ )
+ _simple_status_change_message = _(
+ "This issue has been mentioned in the {platform} commit "
+ "\"{commit_message}\""
+ )
+ try:
+ return _status_change_message.format(platform=self.platform, user_text=user_text, **kwargs)
+ except Exception:
+ return _simple_status_change_message.format(platform=self.platform)
+
+ def get_item_classes(self, ref):
+ if Issue.objects.filter(project=self.project, ref=ref).exists():
+ modelClass = Issue
+ statusClass = IssueStatus
+ elif Task.objects.filter(project=self.project, ref=ref).exists():
+ modelClass = Task
+ statusClass = TaskStatus
+ elif UserStory.objects.filter(project=self.project, ref=ref).exists():
+ modelClass = UserStory
+ statusClass = UserStoryStatus
+ else:
+ raise ActionSyntaxException(_("The referenced element doesn't exist"))
+
+ return (modelClass, statusClass)
+
+ def get_item_by_ref(self, ref):
+ (modelClass, statusClass) = self.get_item_classes(ref)
+
+ return modelClass.objects.get(project=self.project, ref=ref)
+
+ def set_item_status(self, ref, status_slug):
+ (modelClass, statusClass) = self.get_item_classes(ref)
+ element = modelClass.objects.get(project=self.project, ref=ref)
+
+ try:
+ status = statusClass.objects.get(project=self.project, slug=status_slug)
+ except statusClass.DoesNotExist:
+ raise ActionSyntaxException(_("The status doesn't exist"))
+
+ src_status = element.status.name
+ dst_status = status.name
+
+ element.status = status
+ element.save()
+ return (element, src_status, dst_status)
+
+ def process_event(self):
+ if self.ignore():
+ return
+ data = self.get_data()
+
+ for commit in data:
+ consumed_refs = []
+
+ # Status changes
+ p = re.compile("tg-(\d+) +#([-\w]+)")
+ for m in p.finditer(commit['commit_message'].lower()):
+ ref = m.group(1)
+ status_slug = m.group(2)
+ (element, src_status, dst_status) = self.set_item_status(ref, status_slug)
+
+ comment = self.generate_status_change_comment(src_status=src_status, dst_status=dst_status, **commit)
+ snapshot = take_snapshot(element,
+ comment=comment,
+ user=self.get_user(commit['user_id'], self.platform_slug))
+ send_notifications(element, history=snapshot)
+ consumed_refs.append(ref)
+
+ # Reference on commit
+ p = re.compile("tg-(\d+)")
+ for m in p.finditer(commit['commit_message'].lower()):
+ ref = m.group(1)
+ if ref in consumed_refs:
+ continue
+ element = self.get_item_by_ref(ref)
+ type_name = element.__class__._meta.verbose_name
+ comment = self.generate_commit_reference_comment(type_name=type_name, **commit)
+ snapshot = take_snapshot(element,
+ comment=comment,
+ user=self.get_user(commit['user_id'], self.platform_slug))
+ send_notifications(element, history=snapshot)
+ consumed_refs.append(ref)
diff --git a/taiga/hooks/github/event_hooks.py b/taiga/hooks/github/event_hooks.py
index 68e57993..c4ecc300 100644
--- a/taiga/hooks/github/event_hooks.py
+++ b/taiga/hooks/github/event_hooks.py
@@ -16,201 +16,72 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from django.utils.translation import ugettext as _
-
-from taiga.projects.models import IssueStatus, TaskStatus, UserStoryStatus
-
-from taiga.projects.issues.models import Issue
-from taiga.projects.tasks.models import Task
-from taiga.projects.userstories.models import UserStory
-from taiga.projects.history.services import take_snapshot
-from taiga.projects.notifications.services import send_notifications
-from taiga.hooks.event_hooks import BaseEventHook
-from taiga.hooks.exceptions import ActionSyntaxException
-
-from .services import get_github_user
-
import re
-
-class PushEventHook(BaseEventHook):
- def process_event(self):
- if self.payload is None:
- return
-
- github_user = self.payload.get('sender', {})
-
- commits = self.payload.get("commits", [])
- for commit in commits:
- self._process_commit(commit, github_user)
-
- def _process_commit(self, commit, github_user):
- """
- The message we will be looking for seems like
- TG-XX #yyyyyy
- Where:
- XX: is the ref for us, issue or task
- yyyyyy: is the status slug we are setting
- """
- message = commit.get("message", None)
-
- if message is None:
- return
-
- p = re.compile("tg-(\d+) +#([-\w]+)")
- for m in p.finditer(message.lower()):
- ref = m.group(1)
- status_slug = m.group(2)
- self._change_status(ref, status_slug, github_user, commit)
-
- def _change_status(self, ref, status_slug, github_user, commit):
- if Issue.objects.filter(project=self.project, ref=ref).exists():
- modelClass = Issue
- statusClass = IssueStatus
- elif Task.objects.filter(project=self.project, ref=ref).exists():
- modelClass = Task
- statusClass = TaskStatus
- elif UserStory.objects.filter(project=self.project, ref=ref).exists():
- modelClass = UserStory
- statusClass = UserStoryStatus
- else:
- raise ActionSyntaxException(_("The referenced element doesn't exist"))
-
- element = modelClass.objects.get(project=self.project, ref=ref)
-
- try:
- status = statusClass.objects.get(project=self.project, slug=status_slug)
- except statusClass.DoesNotExist:
- raise ActionSyntaxException(_("The status doesn't exist"))
-
- element.status = status
- element.save()
-
- github_user_id = github_user.get('id', None)
- github_user_name = github_user.get('login', None)
- github_user_url = github_user.get('html_url', None)
- commit_id = commit.get("id", None)
- commit_url = commit.get("url", None)
- commit_message = commit.get("message", None)
-
- if (github_user_id and github_user_name and github_user_url and
- commit_id and commit_url and commit_message):
- comment = _("Status changed by [@{github_user_name}]({github_user_url} "
- "\"See @{github_user_name}'s GitHub profile\") "
- "from GitHub commit [{commit_id}]({commit_url} "
- "\"See commit '{commit_id} - {commit_message}'\").").format(
- github_user_name=github_user_name,
- github_user_url=github_user_url,
- commit_id=commit_id[:7],
- commit_url=commit_url,
- commit_message=commit_message)
-
- else:
- comment = _("Status changed from GitHub commit.")
-
- snapshot = take_snapshot(element,
- comment=comment,
- user=get_github_user(github_user_id))
- send_notifications(element, history=snapshot)
+from taiga.hooks.event_hooks import BaseNewIssueEventHook, BaseIssueCommentEventHook, BasePushEventHook
-def replace_github_references(project_url, wiki_text):
- if wiki_text == None:
- wiki_text = ""
+class BaseGitHubEventHook():
+ platform = "GitHub"
+ platform_slug = "github"
- template = "\g<1>[GitHub#\g<2>]({}/issues/\g<2>)\g<3>".format(project_url)
- return re.sub(r"(\s|^)#(\d+)(\s|$)", template, wiki_text, 0, re.M)
+ def replace_github_references(self, project_url, wiki_text):
+ if wiki_text is None:
+ wiki_text = ""
+
+ template = "\g<1>[GitHub#\g<2>]({}/issues/\g<2>)\g<3>".format(project_url)
+ return re.sub(r"(\s|^)#(\d+)(\s|$)", template, wiki_text, 0, re.M)
-class IssuesEventHook(BaseEventHook):
- def process_event(self):
- if self.payload.get('action', None) != "opened":
- return
+class IssuesEventHook(BaseGitHubEventHook, BaseNewIssueEventHook):
+ def ignore(self):
+ return self.payload.get('action', None) != "opened"
- number = self.payload.get('issue', {}).get('number', None)
- subject = self.payload.get('issue', {}).get('title', None)
- github_url = self.payload.get('issue', {}).get('html_url', None)
- github_user_id = self.payload.get('issue', {}).get('user', {}).get('id', None)
- github_user_name = self.payload.get('issue', {}).get('user', {}).get('login', None)
- github_user_url = self.payload.get('issue', {}).get('user', {}).get('html_url', None)
- project_url = self.payload.get('repository', {}).get('html_url', None)
+ def get_data(self):
description = self.payload.get('issue', {}).get('body', None)
- description = replace_github_references(project_url, description)
-
- user = get_github_user(github_user_id)
-
- if not all([subject, github_url, project_url]):
- raise ActionSyntaxException(_("Invalid issue information"))
-
- issue = Issue.objects.create(
- project=self.project,
- subject=subject,
- description=description,
- status=self.project.default_issue_status,
- type=self.project.default_issue_type,
- severity=self.project.default_severity,
- priority=self.project.default_priority,
- external_reference=['github', github_url],
- owner=user
- )
- take_snapshot(issue, user=user)
-
- if number and subject and github_user_name and github_user_url:
- comment = _("Issue created by [@{github_user_name}]({github_user_url} "
- "\"See @{github_user_name}'s GitHub profile\") "
- "from GitHub.\nOrigin GitHub issue: [gh#{number} - {subject}]({github_url} "
- "\"Go to 'gh#{number} - {subject}'\"):\n\n"
- "{description}").format(github_user_name=github_user_name,
- github_user_url=github_user_url,
- number=number,
- subject=subject,
- github_url=github_url,
- description=description)
- else:
- comment = _("Issue created from GitHub.")
-
- snapshot = take_snapshot(issue, comment=comment, user=user)
- send_notifications(issue, history=snapshot)
-
-
-class IssueCommentEventHook(BaseEventHook):
- def process_event(self):
- if self.payload.get('action', None) != "created":
- raise ActionSyntaxException(_("Invalid issue comment information"))
-
- number = self.payload.get('issue', {}).get('number', None)
- subject = self.payload.get('issue', {}).get('title', None)
- github_url = self.payload.get('issue', {}).get('html_url', None)
- github_user_id = self.payload.get('sender', {}).get('id', None)
- github_user_name = self.payload.get('sender', {}).get('login', None)
- github_user_url = self.payload.get('sender', {}).get('html_url', None)
project_url = self.payload.get('repository', {}).get('html_url', None)
+ return {
+ "number": self.payload.get('issue', {}).get('number', None),
+ "subject": self.payload.get('issue', {}).get('title', None),
+ "url": self.payload.get('issue', {}).get('html_url', None),
+ "user_id": self.payload.get('issue', {}).get('user', {}).get('id', None),
+ "user_name": self.payload.get('issue', {}).get('user', {}).get('login', None),
+ "user_url": self.payload.get('issue', {}).get('user', {}).get('html_url', None),
+ "description": self.replace_github_references(project_url, description),
+ }
+
+
+class IssueCommentEventHook(BaseGitHubEventHook, BaseIssueCommentEventHook):
+ def ignore(self):
+ return self.payload.get('action', None) != "created"
+
+ def get_data(self):
comment_message = self.payload.get('comment', {}).get('body', None)
- comment_message = replace_github_references(project_url, comment_message)
+ project_url = self.payload.get('repository', {}).get('html_url', None)
+ return {
+ "number": self.payload.get('issue', {}).get('number', None),
+ "url": self.payload.get('issue', {}).get('html_url', None),
+ "user_id": self.payload.get('sender', {}).get('id', None),
+ "user_name": self.payload.get('sender', {}).get('login', None),
+ "user_url": self.payload.get('sender', {}).get('html_url', None),
+ "comment_url": self.payload.get('comment', {}).get('html_url', None),
+ "comment_message": self.replace_github_references(project_url, comment_message),
+ }
- user = get_github_user(github_user_id)
- if not all([comment_message, github_url, project_url]):
- raise ActionSyntaxException(_("Invalid issue comment information"))
+class PushEventHook(BaseGitHubEventHook, BasePushEventHook):
+ def get_data(self):
+ result = []
+ github_user = self.payload.get('sender', {})
+ commits = self.payload.get("commits", [])
+ for commit in filter(None, commits):
+ result.append({
+ "user_id": github_user.get('id', None),
+ "user_name": github_user.get('login', None),
+ "user_url": github_user.get('html_url', None),
+ "commit_id": commit.get("id", None),
+ "commit_url": commit.get("url", None),
+ "commit_message": commit.get("message", None),
+ })
- issues = Issue.objects.filter(external_reference=["github", github_url])
- tasks = Task.objects.filter(external_reference=["github", github_url])
- uss = UserStory.objects.filter(external_reference=["github", github_url])
-
- for item in list(issues) + list(tasks) + list(uss):
- if number and subject and github_user_name and github_user_url:
- comment = _("Comment by [@{github_user_name}]({github_user_url} "
- "\"See @{github_user_name}'s GitHub profile\") "
- "from GitHub.\nOrigin GitHub issue: [gh#{number} - {subject}]({github_url} "
- "\"Go to 'gh#{number} - {subject}'\")\n\n"
- "{message}").format(github_user_name=github_user_name,
- github_user_url=github_user_url,
- number=number,
- subject=subject,
- github_url=github_url,
- message=comment_message)
- else:
- comment = _("Comment From GitHub:\n\n{message}").format(message=comment_message)
-
- snapshot = take_snapshot(item, comment=comment, user=user)
- send_notifications(item, history=snapshot)
+ return result
diff --git a/taiga/hooks/github/services.py b/taiga/hooks/github/services.py
index cd244ae3..e7286d86 100644
--- a/taiga/hooks/github/services.py
+++ b/taiga/hooks/github/services.py
@@ -18,10 +18,8 @@
import uuid
-from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
-from taiga.users.models import AuthData
from taiga.base.utils.urls import get_absolute_url
@@ -38,18 +36,3 @@ def get_or_generate_config(project):
url = "%s?project=%s" % (url, project.id)
g_config["webhooks_url"] = url
return g_config
-
-
-def get_github_user(github_id):
- user = None
-
- if github_id:
- try:
- user = AuthData.objects.get(key="github", value=github_id).user
- except AuthData.DoesNotExist:
- pass
-
- if user is None:
- user = get_user_model().objects.get(is_system=True, username__startswith="github")
-
- return user
diff --git a/taiga/hooks/gitlab/api.py b/taiga/hooks/gitlab/api.py
index 910ee437..127d7536 100644
--- a/taiga/hooks/gitlab/api.py
+++ b/taiga/hooks/gitlab/api.py
@@ -70,14 +70,6 @@ class GitLabViewSet(BaseWebhookApiViewSet):
return project_secret == secret_key
- def _get_project(self, request):
- project_id = request.GET.get("project", None)
- try:
- project = Project.objects.get(id=project_id)
- return project
- except Project.DoesNotExist:
- return None
-
def _get_event_name(self, request):
payload = json.loads(request.body.decode("utf-8"))
return payload.get('object_kind', 'push') if payload is not None else 'empty'
diff --git a/taiga/hooks/gitlab/event_hooks.py b/taiga/hooks/gitlab/event_hooks.py
index aff09e2f..5b4b4006 100644
--- a/taiga/hooks/gitlab/event_hooks.py
+++ b/taiga/hooks/gitlab/event_hooks.py
@@ -19,158 +19,71 @@
import re
import os
-from django.utils.translation import ugettext as _
-
-from taiga.projects.models import IssueStatus, TaskStatus, UserStoryStatus
-
-from taiga.projects.issues.models import Issue
-from taiga.projects.tasks.models import Task
-from taiga.projects.userstories.models import UserStory
-from taiga.projects.history.services import take_snapshot
-from taiga.projects.notifications.services import send_notifications
-from taiga.hooks.event_hooks import BaseEventHook
-from taiga.hooks.exceptions import ActionSyntaxException
-
-from .services import get_gitlab_user
+from taiga.hooks.event_hooks import BaseNewIssueEventHook, BaseIssueCommentEventHook, BasePushEventHook
-class PushEventHook(BaseEventHook):
- def process_event(self):
- if self.payload is None:
- return
+class BaseGitLabEventHook():
+ platform = "GitLab"
+ platform_slug = "gitlab"
- commits = self.payload.get("commits", [])
- for commit in commits:
- message = commit.get("message", None)
- self._process_message(message, None)
+ def replace_gitlab_references(self, project_url, wiki_text):
+ if wiki_text is None:
+ wiki_text = ""
- def _process_message(self, message, gitlab_user):
- """
- The message we will be looking for seems like
- TG-XX #yyyyyy
- Where:
- XX: is the ref for us, issue or task
- yyyyyy: is the status slug we are setting
- """
- if message is None:
- return
-
- p = re.compile("tg-(\d+) +#([-\w]+)")
- for m in p.finditer(message.lower()):
- ref = m.group(1)
- status_slug = m.group(2)
- self._change_status(ref, status_slug, gitlab_user)
-
- def _change_status(self, ref, status_slug, gitlab_user):
- if Issue.objects.filter(project=self.project, ref=ref).exists():
- modelClass = Issue
- statusClass = IssueStatus
- elif Task.objects.filter(project=self.project, ref=ref).exists():
- modelClass = Task
- statusClass = TaskStatus
- elif UserStory.objects.filter(project=self.project, ref=ref).exists():
- modelClass = UserStory
- statusClass = UserStoryStatus
- else:
- raise ActionSyntaxException(_("The referenced element doesn't exist"))
-
- element = modelClass.objects.get(project=self.project, ref=ref)
-
- try:
- status = statusClass.objects.get(project=self.project, slug=status_slug)
- except statusClass.DoesNotExist:
- raise ActionSyntaxException(_("The status doesn't exist"))
-
- element.status = status
- element.save()
-
- snapshot = take_snapshot(element,
- comment=_("Status changed from GitLab commit"),
- user=get_gitlab_user(gitlab_user))
- send_notifications(element, history=snapshot)
+ template = "\g<1>[GitLab#\g<2>]({}/issues/\g<2>)\g<3>".format(project_url)
+ return re.sub(r"(\s|^)#(\d+)(\s|$)", template, wiki_text, 0, re.M)
-def replace_gitlab_references(project_url, wiki_text):
- if wiki_text is None:
- wiki_text = ""
+class IssuesEventHook(BaseGitLabEventHook, BaseNewIssueEventHook):
+ def ignore(self):
+ return self.payload.get('object_attributes', {}).get("action", "") != "open"
- template = "\g<1>[GitLab#\g<2>]({}/issues/\g<2>)\g<3>".format(project_url)
- return re.sub(r"(\s|^)#(\d+)(\s|$)", template, wiki_text, 0, re.M)
-
-
-class IssuesEventHook(BaseEventHook):
- def process_event(self):
- if self.payload.get('object_attributes', {}).get("action", "") != "open":
- return
-
- subject = self.payload.get('object_attributes', {}).get('title', None)
+ def get_data(self):
description = self.payload.get('object_attributes', {}).get('description', None)
- gitlab_reference = self.payload.get('object_attributes', {}).get('url', None)
-
- project_url = None
- if gitlab_reference:
- project_url = os.path.basename(os.path.basename(gitlab_reference))
-
- if not all([subject, gitlab_reference, project_url]):
- raise ActionSyntaxException(_("Invalid issue information"))
-
- issue = Issue.objects.create(
- project=self.project,
- subject=subject,
- description=replace_gitlab_references(project_url, description),
- status=self.project.default_issue_status,
- type=self.project.default_issue_type,
- severity=self.project.default_severity,
- priority=self.project.default_priority,
- external_reference=['gitlab', gitlab_reference],
- owner=get_gitlab_user(None)
- )
- take_snapshot(issue, user=get_gitlab_user(None))
-
- snapshot = take_snapshot(issue, comment=_("Created from GitLab"), user=get_gitlab_user(None))
- send_notifications(issue, history=snapshot)
-
-
-class IssueCommentEventHook(BaseEventHook):
- def process_event(self):
- if self.payload.get('object_attributes', {}).get("noteable_type", None) != "Issue":
- return
-
- number = self.payload.get('issue', {}).get('iid', None)
- subject = self.payload.get('issue', {}).get('title', None)
-
project_url = self.payload.get('repository', {}).get('homepage', None)
+ user_name = self.payload.get('user', {}).get('username', None)
+ return {
+ "number": self.payload.get('object_attributes', {}).get('iid', None),
+ "subject": self.payload.get('object_attributes', {}).get('title', None),
+ "url": self.payload.get('object_attributes', {}).get('url', None),
+ "user_id": None,
+ "user_name": user_name,
+ "user_url": os.path.join(os.path.dirname(os.path.dirname(project_url)), "u", user_name),
+ "description": self.replace_gitlab_references(project_url, description),
+ }
- gitlab_url = os.path.join(project_url, "issues", str(number))
- gitlab_user_name = self.payload.get('user', {}).get('username', None)
- gitlab_user_url = os.path.join(os.path.dirname(os.path.dirname(project_url)), "u", gitlab_user_name)
+class IssueCommentEventHook(BaseGitLabEventHook, BaseIssueCommentEventHook):
+ def ignore(self):
+ return self.payload.get('object_attributes', {}).get("noteable_type", None) != "Issue"
+
+ def get_data(self):
comment_message = self.payload.get('object_attributes', {}).get('note', None)
- comment_message = replace_gitlab_references(project_url, comment_message)
+ project_url = self.payload.get('repository', {}).get('homepage', None)
+ number = self.payload.get('issue', {}).get('iid', None)
+ user_name = self.payload.get('user', {}).get('username', None)
+ return {
+ "number": number,
+ "url": os.path.join(project_url, "issues", str(number)),
+ "user_id": None,
+ "user_name": user_name,
+ "user_url": os.path.join(os.path.dirname(os.path.dirname(project_url)), "u", user_name),
+ "comment_url": self.payload.get('object_attributes', {}).get('url', None),
+ "comment_message": self.replace_gitlab_references(project_url, comment_message),
+ }
- user = get_gitlab_user(None)
- if not all([comment_message, gitlab_url, project_url]):
- raise ActionSyntaxException(_("Invalid issue comment information"))
-
- issues = Issue.objects.filter(external_reference=["gitlab", gitlab_url])
- tasks = Task.objects.filter(external_reference=["gitlab", gitlab_url])
- uss = UserStory.objects.filter(external_reference=["gitlab", gitlab_url])
-
- for item in list(issues) + list(tasks) + list(uss):
- if number and subject and gitlab_user_name and gitlab_user_url:
- comment = _("Comment by [@{gitlab_user_name}]({gitlab_user_url} "
- "\"See @{gitlab_user_name}'s GitLab profile\") "
- "from GitLab.\nOrigin GitLab issue: [gl#{number} - {subject}]({gitlab_url} "
- "\"Go to 'gl#{number} - {subject}'\")\n\n"
- "{message}").format(gitlab_user_name=gitlab_user_name,
- gitlab_user_url=gitlab_user_url,
- number=number,
- subject=subject,
- gitlab_url=gitlab_url,
- message=comment_message)
- else:
- comment = _("Comment From GitLab:\n\n{message}").format(message=comment_message)
-
- snapshot = take_snapshot(item, comment=comment, user=user)
- send_notifications(item, history=snapshot)
+class PushEventHook(BaseGitLabEventHook, BasePushEventHook):
+ def get_data(self):
+ result = []
+ for commit in self.payload.get("commits", []):
+ user_name = commit.get('author', {}).get('name', None)
+ result.append({
+ "user_id": None,
+ "user_name": user_name,
+ "user_url": None,
+ "commit_id": commit.get("id", None),
+ "commit_url": commit.get("url", None),
+ "commit_message": commit.get("message").strip(),
+ })
+ return result
diff --git a/taiga/hooks/gitlab/services.py b/taiga/hooks/gitlab/services.py
index cd4751fb..a31352ed 100644
--- a/taiga/hooks/gitlab/services.py
+++ b/taiga/hooks/gitlab/services.py
@@ -18,7 +18,6 @@
import uuid
-from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from django.conf import settings
@@ -41,18 +40,3 @@ def get_or_generate_config(project):
url = "{}?project={}&key={}".format(url, project.id, g_config["secret"])
g_config["webhooks_url"] = url
return g_config
-
-
-def get_gitlab_user(user_email):
- user = None
-
- if user_email:
- try:
- user = get_user_model().objects.get(email=user_email)
- except get_user_model().DoesNotExist:
- pass
-
- if user is None:
- user = get_user_model().objects.get(is_system=True, username__startswith="gitlab")
-
- return user
diff --git a/taiga/hooks/gogs/__init__.py b/taiga/hooks/gogs/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/taiga/hooks/gogs/api.py b/taiga/hooks/gogs/api.py
new file mode 100644
index 00000000..ced551de
--- /dev/null
+++ b/taiga/hooks/gogs/api.py
@@ -0,0 +1,44 @@
+# Copyright (C) 2014-2016 Andrey Antukh
+# Copyright (C) 2014-2016 Jesús Espino
+# Copyright (C) 2014-2016 David Barragán
+# Copyright (C) 2014-2016 Alejandro Alonso
+# 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 .
+
+from taiga.hooks.api import BaseWebhookApiViewSet
+
+from . import event_hooks
+
+
+class GogsViewSet(BaseWebhookApiViewSet):
+ event_hook_classes = {
+ "push": event_hooks.PushEventHook
+ }
+
+ def _validate_signature(self, project, request):
+ payload = self._get_payload(request)
+
+ if not hasattr(project, "modules_config"):
+ return False
+
+ if project.modules_config.config is None:
+ return False
+
+ secret = project.modules_config.config.get("gogs", {}).get("secret", None)
+ if secret is None:
+ return False
+
+ return payload.get('secret', None) == secret
+
+ def _get_event_name(self, request):
+ return "push"
diff --git a/taiga/hooks/gogs/event_hooks.py b/taiga/hooks/gogs/event_hooks.py
new file mode 100644
index 00000000..8e68b8db
--- /dev/null
+++ b/taiga/hooks/gogs/event_hooks.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2014-2016 Andrey Antukh
+# Copyright (C) 2014-2016 Jesús Espino
+# Copyright (C) 2014-2016 David Barragán
+# Copyright (C) 2014-2016 Alejandro Alonso
+# 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 .
+
+import re
+import os.path
+
+from taiga.hooks.event_hooks import BasePushEventHook
+
+
+class BaseGogsEventHook():
+ platform = "Gogs"
+ platform_slug = "gogs"
+
+ def replace_gogs_references(self, project_url, wiki_text):
+ if wiki_text is None:
+ wiki_text = ""
+
+ template = "\g<1>[Gogs#\g<2>]({}/issues/\g<2>)\g<3>".format(project_url)
+ return re.sub(r"(\s|^)#(\d+)(\s|$)", template, wiki_text, 0, re.M)
+
+
+class PushEventHook(BaseGogsEventHook, BasePushEventHook):
+ def get_data(self):
+ result = []
+ commits = self.payload.get("commits", [])
+ project_url = self.payload.get("repository", {}).get("url", None)
+
+ for commit in filter(None, commits):
+ user_name = commit.get('author', {}).get('username', None)
+ result.append({
+ "user_id": user_name,
+ "user_name": user_name,
+ "user_url": os.path.join(os.path.dirname(os.path.dirname(project_url)), user_name),
+ "commit_id": commit.get("id", None),
+ "commit_url": commit.get("url", None),
+ "commit_message": commit.get("message", None),
+ })
+ return result
diff --git a/taiga/hooks/gogs/migrations/0001_initial.py b/taiga/hooks/gogs/migrations/0001_initial.py
new file mode 100644
index 00000000..09ba6709
--- /dev/null
+++ b/taiga/hooks/gogs/migrations/0001_initial.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.core.files import File
+
+import uuid
+import os
+
+CUR_DIR = os.path.dirname(__file__)
+
+
+def create_gogs_system_user(apps, schema_editor):
+ # We get the model from the versioned app registry;
+ # if we directly import it, it'll be the wrong version
+ User = apps.get_model("users", "User")
+ db_alias = schema_editor.connection.alias
+ random_hash = uuid.uuid4().hex
+ user = User.objects.using(db_alias).create(
+ username="gogs-{}".format(random_hash),
+ email="gogs-{}@taiga.io".format(random_hash),
+ full_name="Gogs",
+ is_active=False,
+ is_system=True,
+ bio="",
+ )
+ f = open("{}/logo.png".format(CUR_DIR), "rb")
+ user.photo.save("logo.png", File(f))
+ user.save()
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('users', '0010_auto_20150414_0936')
+ ]
+
+ operations = [
+ migrations.RunPython(create_gogs_system_user),
+ ]
diff --git a/taiga/hooks/gogs/migrations/__init__.py b/taiga/hooks/gogs/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/taiga/hooks/gogs/migrations/logo.png b/taiga/hooks/gogs/migrations/logo.png
new file mode 100644
index 00000000..384a58d2
Binary files /dev/null and b/taiga/hooks/gogs/migrations/logo.png differ
diff --git a/taiga/hooks/gogs/models.py b/taiga/hooks/gogs/models.py
new file mode 100644
index 00000000..fca83d73
--- /dev/null
+++ b/taiga/hooks/gogs/models.py
@@ -0,0 +1 @@
+# This file is needed to load migrations
diff --git a/taiga/hooks/gogs/services.py b/taiga/hooks/gogs/services.py
new file mode 100644
index 00000000..40d06fab
--- /dev/null
+++ b/taiga/hooks/gogs/services.py
@@ -0,0 +1,37 @@
+# Copyright (C) 2014-2016 Andrey Antukh
+# Copyright (C) 2014-2016 Jesús Espino
+# Copyright (C) 2014-2016 David Barragán
+# Copyright (C) 2014-2016 Alejandro Alonso
+# 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 .
+
+import uuid
+
+from django.core.urlresolvers import reverse
+
+from taiga.base.utils.urls import get_absolute_url
+
+
+# Set this in settings.PROJECT_MODULES_CONFIGURATORS["gogs"]
+def get_or_generate_config(project):
+ config = project.modules_config.config
+ if config and "gogs" in config:
+ g_config = project.modules_config.config["gogs"]
+ else:
+ g_config = {"secret": uuid.uuid4().hex}
+
+ url = reverse("gogs-hook-list")
+ url = get_absolute_url(url)
+ url = "%s?project=%s" % (url, project.id)
+ g_config["webhooks_url"] = url
+ return g_config
diff --git a/taiga/routers.py b/taiga/routers.py
index 66e1b9f7..24974b74 100644
--- a/taiga/routers.py
+++ b/taiga/routers.py
@@ -208,6 +208,12 @@ from taiga.hooks.bitbucket.api import BitBucketViewSet
router.register(r"bitbucket-hook", BitBucketViewSet, base_name="bitbucket-hook")
+# Gogs webhooks
+from taiga.hooks.gogs.api import GogsViewSet
+
+router.register(r"gogs-hook", GogsViewSet, base_name="gogs-hook")
+
+
# Importer
from taiga.export_import.api import ProjectImporterViewSet, ProjectExporterViewSet
diff --git a/tests/integration/test_hooks_bitbucket.py b/tests/integration/test_hooks_bitbucket.py
index 0f74078e..90023b38 100644
--- a/tests/integration/test_hooks_bitbucket.py
+++ b/tests/integration/test_hooks_bitbucket.py
@@ -246,6 +246,13 @@ def test_push_event_issue_processing(client):
new_status = f.IssueStatusFactory(project=creation_status.project)
issue = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = {
+ "actor": {
+ "user": {
+ "uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
+ "username": "test-user",
+ "links": {"html": {"href": "http://bitbucket.com/test-user"}}
+ }
+ },
"push": {
"changes": [
{
@@ -271,6 +278,13 @@ def test_push_event_task_processing(client):
new_status = f.TaskStatusFactory(project=creation_status.project)
task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = {
+ "actor": {
+ "user": {
+ "uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
+ "username": "test-user",
+ "links": {"html": {"href": "http://bitbucket.com/test-user"}}
+ }
+ },
"push": {
"changes": [
{
@@ -296,6 +310,13 @@ def test_push_event_user_story_processing(client):
new_status = f.UserStoryStatusFactory(project=creation_status.project)
user_story = f.UserStoryFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = {
+ "actor": {
+ "user": {
+ "uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
+ "username": "test-user",
+ "links": {"html": {"href": "http://bitbucket.com/test-user"}}
+ }
+ },
"push": {
"changes": [
{
@@ -314,6 +335,108 @@ def test_push_event_user_story_processing(client):
assert len(mail.outbox) == 1
+def test_push_event_issue_mention(client):
+ creation_status = f.IssueStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_issues"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ issue = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ take_snapshot(issue, user=creation_status.project.owner)
+ payload = {
+ "actor": {
+ "user": {
+ "uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
+ "username": "test-user",
+ "links": {"html": {"href": "http://bitbucket.com/test-user"}}
+ }
+ },
+ "push": {
+ "changes": [
+ {
+ "commits": [
+ { "message": "test message test TG-%s ok bye!" % (issue.ref) }
+ ]
+ }
+ ]
+ }
+ }
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(issue.project, payload)
+ ev_hook.process_event()
+ issue_history = get_history_queryset_by_model_instance(issue)
+ assert issue_history.count() == 1
+ assert issue_history[0].comment.startswith("This issue has been mentioned by")
+ assert len(mail.outbox) == 1
+
+
+def test_push_event_task_mention(client):
+ creation_status = f.TaskStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_tasks"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ take_snapshot(task, user=creation_status.project.owner)
+ payload = {
+ "actor": {
+ "user": {
+ "uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
+ "username": "test-user",
+ "links": {"html": {"href": "http://bitbucket.com/test-user"}}
+ }
+ },
+ "push": {
+ "changes": [
+ {
+ "commits": [
+ { "message": "test message test TG-%s ok bye!" % (task.ref) }
+ ]
+ }
+ ]
+ }
+ }
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(task.project, payload)
+ ev_hook.process_event()
+ task_history = get_history_queryset_by_model_instance(task)
+ assert task_history.count() == 1
+ assert task_history[0].comment.startswith("This task has been mentioned by")
+ assert len(mail.outbox) == 1
+
+
+def test_push_event_user_story_mention(client):
+ creation_status = f.UserStoryStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_us"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ user_story = f.UserStoryFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ take_snapshot(user_story, user=creation_status.project.owner)
+ payload = {
+ "actor": {
+ "user": {
+ "uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
+ "username": "test-user",
+ "links": {"html": {"href": "http://bitbucket.com/test-user"}}
+ }
+ },
+ "push": {
+ "changes": [
+ {
+ "commits": [
+ { "message": "test message test TG-%s ok bye!" % (user_story.ref) }
+ ]
+ }
+ ]
+ }
+ }
+
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(user_story.project, payload)
+ ev_hook.process_event()
+ us_history = get_history_queryset_by_model_instance(user_story)
+ assert us_history.count() == 1
+ assert us_history[0].comment.startswith("This user story has been mentioned by")
+ assert len(mail.outbox) == 1
+
+
+
+
def test_push_event_multiple_actions(client):
creation_status = f.IssueStatusFactory()
role = f.RoleFactory(project=creation_status.project, permissions=["view_issues"])
@@ -322,6 +445,13 @@ def test_push_event_multiple_actions(client):
issue1 = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
issue2 = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = {
+ "actor": {
+ "user": {
+ "uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
+ "username": "test-user",
+ "links": {"html": {"href": "http://bitbucket.com/test-user"}}
+ }
+ },
"push": {
"changes": [
{
@@ -349,6 +479,13 @@ def test_push_event_processing_case_insensitive(client):
new_status = f.TaskStatusFactory(project=creation_status.project)
task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = {
+ "actor": {
+ "user": {
+ "uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
+ "username": "test-user",
+ "links": {"html": {"href": "http://bitbucket.com/test-user"}}
+ }
+ },
"push": {
"changes": [
{
@@ -370,6 +507,13 @@ def test_push_event_processing_case_insensitive(client):
def test_push_event_task_bad_processing_non_existing_ref(client):
issue_status = f.IssueStatusFactory()
payload = {
+ "actor": {
+ "user": {
+ "uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
+ "username": "test-user",
+ "links": {"html": {"href": "http://bitbucket.com/test-user"}}
+ }
+ },
"push": {
"changes": [
{
@@ -393,6 +537,13 @@ def test_push_event_task_bad_processing_non_existing_ref(client):
def test_push_event_us_bad_processing_non_existing_status(client):
user_story = f.UserStoryFactory.create()
payload = {
+ "actor": {
+ "user": {
+ "uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
+ "username": "test-user",
+ "links": {"html": {"href": "http://bitbucket.com/test-user"}}
+ }
+ },
"push": {
"changes": [
{
@@ -417,6 +568,13 @@ def test_push_event_us_bad_processing_non_existing_status(client):
def test_push_event_bad_processing_non_existing_status(client):
issue = f.IssueFactory.create()
payload = {
+ "actor": {
+ "user": {
+ "uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
+ "username": "test-user",
+ "links": {"html": {"href": "http://bitbucket.com/test-user"}}
+ }
+ },
"push": {
"changes": [
{
@@ -686,8 +844,10 @@ def test_api_patch_project_modules(client):
def test_replace_bitbucket_references():
- assert event_hooks.replace_bitbucket_references("project-url", "#2") == "[BitBucket#2](project-url/issues/2)"
- assert event_hooks.replace_bitbucket_references("project-url", "#2 ") == "[BitBucket#2](project-url/issues/2) "
- assert event_hooks.replace_bitbucket_references("project-url", " #2 ") == " [BitBucket#2](project-url/issues/2) "
- assert event_hooks.replace_bitbucket_references("project-url", " #2") == " [BitBucket#2](project-url/issues/2)"
- assert event_hooks.replace_bitbucket_references("project-url", "#test") == "#test"
+ ev_hook = event_hooks.BaseBitBucketEventHook
+ assert ev_hook.replace_bitbucket_references(None, "project-url", "#2") == "[BitBucket#2](project-url/issues/2)"
+ assert ev_hook.replace_bitbucket_references(None, "project-url", "#2 ") == "[BitBucket#2](project-url/issues/2) "
+ assert ev_hook.replace_bitbucket_references(None, "project-url", " #2 ") == " [BitBucket#2](project-url/issues/2) "
+ assert ev_hook.replace_bitbucket_references(None, "project-url", " #2") == " [BitBucket#2](project-url/issues/2)"
+ assert ev_hook.replace_bitbucket_references(None, "project-url", "#test") == "#test"
+ assert ev_hook.replace_bitbucket_references(None, "project-url", None) == ""
diff --git a/tests/integration/test_hooks_github.py b/tests/integration/test_hooks_github.py
index 4cdeec76..815aba49 100644
--- a/tests/integration/test_hooks_github.py
+++ b/tests/integration/test_hooks_github.py
@@ -172,6 +172,70 @@ def test_push_event_user_story_processing(client):
assert len(mail.outbox) == 1
+def test_push_event_issue_mention(client):
+ creation_status = f.IssueStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_issues"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ issue = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ take_snapshot(issue, user=creation_status.project.owner)
+ payload = {"commits": [
+ {"message": """test message
+ test TG-%s ok
+ bye!
+ """ % (issue.ref)},
+ ]}
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(issue.project, payload)
+ ev_hook.process_event()
+ issue_history = get_history_queryset_by_model_instance(issue)
+ assert issue_history.count() == 1
+ assert issue_history[0].comment.startswith("This issue has been mentioned by")
+ assert len(mail.outbox) == 1
+
+
+def test_push_event_task_mention(client):
+ creation_status = f.TaskStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_tasks"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ take_snapshot(task, user=creation_status.project.owner)
+ payload = {"commits": [
+ {"message": """test message
+ test TG-%s ok
+ bye!
+ """ % (task.ref)},
+ ]}
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(task.project, payload)
+ ev_hook.process_event()
+ task_history = get_history_queryset_by_model_instance(task)
+ assert task_history.count() == 1
+ assert task_history[0].comment.startswith("This task has been mentioned by")
+ assert len(mail.outbox) == 1
+
+
+def test_push_event_user_story_mention(client):
+ creation_status = f.UserStoryStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_us"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ user_story = f.UserStoryFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ take_snapshot(user_story, user=creation_status.project.owner)
+ payload = {"commits": [
+ {"message": """test message
+ test TG-%s ok
+ bye!
+ """ % (user_story.ref)},
+ ]}
+
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(user_story.project, payload)
+ ev_hook.process_event()
+ us_history = get_history_queryset_by_model_instance(user_story)
+ assert us_history.count() == 1
+ assert us_history[0].comment.startswith("This user story has been mentioned by")
+ assert len(mail.outbox) == 1
+
+
def test_push_event_multiple_actions(client):
creation_status = f.IssueStatusFactory()
role = f.RoleFactory(project=creation_status.project, permissions=["view_issues"])
@@ -454,7 +518,7 @@ def test_issues_event_bad_comment(client):
take_snapshot(issue, user=issue.owner)
payload = {
- "action": "other",
+ "action": "created",
"issue": {},
"comment": {},
"repository": {
@@ -512,9 +576,10 @@ def test_api_patch_project_modules(client):
def test_replace_github_references():
- assert event_hooks.replace_github_references("project-url", "#2") == "[GitHub#2](project-url/issues/2)"
- assert event_hooks.replace_github_references("project-url", "#2 ") == "[GitHub#2](project-url/issues/2) "
- assert event_hooks.replace_github_references("project-url", " #2 ") == " [GitHub#2](project-url/issues/2) "
- assert event_hooks.replace_github_references("project-url", " #2") == " [GitHub#2](project-url/issues/2)"
- assert event_hooks.replace_github_references("project-url", "#test") == "#test"
- assert event_hooks.replace_github_references("project-url", None) == ""
+ ev_hook = event_hooks.BaseGitHubEventHook
+ assert ev_hook.replace_github_references(None, "project-url", "#2") == "[GitHub#2](project-url/issues/2)"
+ assert ev_hook.replace_github_references(None, "project-url", "#2 ") == "[GitHub#2](project-url/issues/2) "
+ assert ev_hook.replace_github_references(None, "project-url", " #2 ") == " [GitHub#2](project-url/issues/2) "
+ assert ev_hook.replace_github_references(None, "project-url", " #2") == " [GitHub#2](project-url/issues/2)"
+ assert ev_hook.replace_github_references(None, "project-url", "#test") == "#test"
+ assert ev_hook.replace_github_references(None, "project-url", None) == ""
diff --git a/tests/integration/test_hooks_gitlab.py b/tests/integration/test_hooks_gitlab.py
index f90f74b0..5208a939 100644
--- a/tests/integration/test_hooks_gitlab.py
+++ b/tests/integration/test_hooks_gitlab.py
@@ -18,6 +18,7 @@
# along with this program. If not, see .
import pytest
+from copy import deepcopy
from unittest import mock
@@ -41,6 +42,189 @@ from .. import factories as f
pytestmark = pytest.mark.django_db
+push_base_payload = {
+ "object_kind": "push",
+ "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
+ "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "ref": "refs/heads/master",
+ "checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "user_id": 4,
+ "user_name": "John Smith",
+ "user_email": "john@example.com",
+ "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
+ "project_id": 15,
+ "project": {
+ "name": "Diaspora",
+ "description": "",
+ "web_url": "http://example.com/mike/diaspora",
+ "avatar_url": None,
+ "git_ssh_url": "git@example.com:mike/diaspora.git",
+ "git_http_url": "http://example.com/mike/diaspora.git",
+ "namespace": "Mike",
+ "visibility_level": 0,
+ "path_with_namespace": "mike/diaspora",
+ "default_branch": "master",
+ "homepage": "http://example.com/mike/diaspora",
+ "url": "git@example.com:mike/diaspora.git",
+ "ssh_url": "git@example.com:mike/diaspora.git",
+ "http_url": "http://example.com/mike/diaspora.git"
+ },
+ "repository": {
+ "name": "Diaspora",
+ "url": "git@example.com:mike/diaspora.git",
+ "description": "",
+ "homepage": "http://example.com/mike/diaspora",
+ "git_http_url": "http://example.com/mike/diaspora.git",
+ "git_ssh_url": "git@example.com:mike/diaspora.git",
+ "visibility_level": 0
+ },
+ "commits": [
+ {
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "message": "Update Catalan translation to e38cb41.",
+ "timestamp": "2011-12-12T14:27:31+02:00",
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "author": {
+ "name": "Jordi Mallach",
+ "email": "jordi@softcatala.org"
+ },
+ "added": ["CHANGELOG"],
+ "modified": ["app/controller/application.rb"],
+ "removed": []
+ },
+ {
+ "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "message": "fixed readme",
+ "timestamp": "2012-01-03T23:36:29+02:00",
+ "url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "author": {
+ "name": "GitLab dev user",
+ "email": "gitlabdev@dv6700.(none)"
+ },
+ "added": ["CHANGELOG"],
+ "modified": ["app/controller/application.rb"],
+ "removed": []
+ }
+ ],
+ "total_commits_count": 4
+}
+
+new_issue_base_payload = {
+ "object_kind": "issue",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
+ "project": {
+ "name": "Gitlab Test",
+ "description": "Aut reprehenderit ut est.",
+ "web_url": "http://example.com/gitlabhq/gitlab-test",
+ "avatar_url": None,
+ "git_ssh_url": "git@example.com:gitlabhq/gitlab-test.git",
+ "git_http_url": "http://example.com/gitlabhq/gitlab-test.git",
+ "namespace": "GitlabHQ",
+ "visibility_level": 20,
+ "path_with_namespace": "gitlabhq/gitlab-test",
+ "default_branch": "master",
+ "homepage": "http://example.com/gitlabhq/gitlab-test",
+ "url": "http://example.com/gitlabhq/gitlab-test.git",
+ "ssh_url": "git@example.com:gitlabhq/gitlab-test.git",
+ "http_url": "http://example.com/gitlabhq/gitlab-test.git"
+ },
+ "repository": {
+ "name": "Gitlab Test",
+ "url": "http://example.com/gitlabhq/gitlab-test.git",
+ "description": "Aut reprehenderit ut est.",
+ "homepage": "http://example.com/gitlabhq/gitlab-test"
+ },
+ "object_attributes": {
+ "id": 301,
+ "title": "New API: create/update/delete file",
+ "assignee_id": 51,
+ "author_id": 51,
+ "project_id": 14,
+ "created_at": "2013-12-03T17:15:43Z",
+ "updated_at": "2013-12-03T17:15:43Z",
+ "position": 0,
+ "branch_name": None,
+ "description": "Create new API for manipulations with repository",
+ "milestone_id": None,
+ "state": "opened",
+ "iid": 23,
+ "url": "http://example.com/diaspora/issues/23",
+ "action": "open"
+ },
+ "assignee": {
+ "name": "User1",
+ "username": "user1",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ }
+}
+
+issue_comment_base_payload = {
+ "object_kind": "note",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
+ "project_id": 5,
+ "project": {
+ "name": "Gitlab Test",
+ "description": "Aut reprehenderit ut est.",
+ "web_url": "http://example.com/gitlab-org/gitlab-test",
+ "avatar_url": None,
+ "git_ssh_url": "git@example.com:gitlab-org/gitlab-test.git",
+ "git_http_url": "http://example.com/gitlab-org/gitlab-test.git",
+ "namespace": "Gitlab Org",
+ "visibility_level": 10,
+ "path_with_namespace": "gitlab-org/gitlab-test",
+ "default_branch": "master",
+ "homepage": "http://example.com/gitlab-org/gitlab-test",
+ "url": "http://example.com/gitlab-org/gitlab-test.git",
+ "ssh_url": "git@example.com:gitlab-org/gitlab-test.git",
+ "http_url": "http://example.com/gitlab-org/gitlab-test.git"
+ },
+ "repository": {
+ "name": "diaspora",
+ "url": "git@example.com:mike/diaspora.git",
+ "description": "",
+ "homepage": "http://example.com/mike/diaspora"
+ },
+ "object_attributes": {
+ "id": 1241,
+ "note": "Hello world",
+ "noteable_type": "Issue",
+ "author_id": 1,
+ "created_at": "2015-05-17 17:06:40 UTC",
+ "updated_at": "2015-05-17 17:06:40 UTC",
+ "project_id": 5,
+ "attachment": None,
+ "line_code": None,
+ "commit_id": "",
+ "noteable_id": 92,
+ "system": False,
+ "st_diff": None,
+ "url": "http://example.com/gitlab-org/gitlab-test/issues/17#note_1241"
+ },
+ "issue": {
+ "id": 92,
+ "title": "test",
+ "assignee_id": None,
+ "author_id": 1,
+ "project_id": 5,
+ "created_at": "2015-04-12 14:53:17 UTC",
+ "updated_at": "2015-04-26 08:28:42 UTC",
+ "position": 0,
+ "branch_name": None,
+ "description": "test",
+ "milestone_id": None,
+ "state": "closed",
+ "iid": 17
+ }
+}
def test_bad_signature(client):
project = f.ProjectFactory()
@@ -90,8 +274,8 @@ def test_ok_empty_payload(client):
url = reverse("gitlab-hook-list")
url = "{}?project={}&key={}".format(url, project.id, "tpnIwJDz4e")
- data = {}
- response = client.post(url,"null", content_type="application/json", REMOTE_ADDR="111.111.111.111")
+ response = client.post(url, "null", content_type="application/json",
+ REMOTE_ADDR="111.111.111.111")
assert response.status_code == 204
@@ -108,8 +292,7 @@ def test_ok_signature_ip_in_network(client):
url = reverse("gitlab-hook-list")
url = "{}?project={}&key={}".format(url, project.id, "tpnIwJDz4e")
data = {"test:": "data"}
- response = client.post(url,
- json.dumps(data),
+ response = client.post(url, json.dumps(data),
content_type="application/json",
REMOTE_ADDR="111.111.111.112")
@@ -243,9 +426,13 @@ def test_push_event_detected(client):
project = f.ProjectFactory()
url = reverse("gitlab-hook-list")
url = "%s?project=%s" % (url, project.id)
- data = {"commits": [
- {"message": "test message"},
- ]}
+ data = deepcopy(push_base_payload)
+ data["commits"] = [{
+ "message": "test message",
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ }]
+ data["total_commits_count"] = 1
GitLabViewSet._validate_signature = mock.Mock(return_value=True)
@@ -265,12 +452,16 @@ def test_push_event_issue_processing(client):
f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
new_status = f.IssueStatusFactory(project=creation_status.project)
issue = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
- payload = {"commits": [
- {"message": """test message
- test TG-%s #%s ok
- bye!
- """ % (issue.ref, new_status.slug)},
- ]}
+ payload = deepcopy(push_base_payload)
+ payload["commits"] = [{
+ "message": """test message
+ test TG-%s #%s ok
+ bye!
+ """ % (issue.ref, new_status.slug),
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ }]
+ payload["total_commits_count"] = 1
mail.outbox = []
ev_hook = event_hooks.PushEventHook(issue.project, payload)
ev_hook.process_event()
@@ -285,12 +476,16 @@ def test_push_event_task_processing(client):
f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
new_status = f.TaskStatusFactory(project=creation_status.project)
task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
- payload = {"commits": [
- {"message": """test message
+ payload = deepcopy(push_base_payload)
+ payload["commits"] = [{
+ "message": """test message
test TG-%s #%s ok
bye!
- """ % (task.ref, new_status.slug)},
- ]}
+ """ % (task.ref, new_status.slug),
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ }]
+ payload["total_commits_count"] = 1
mail.outbox = []
ev_hook = event_hooks.PushEventHook(task.project, payload)
ev_hook.process_event()
@@ -305,12 +500,16 @@ def test_push_event_user_story_processing(client):
f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
new_status = f.UserStoryStatusFactory(project=creation_status.project)
user_story = f.UserStoryFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
- payload = {"commits": [
- {"message": """test message
+ payload = deepcopy(push_base_payload)
+ payload["commits"] = [{
+ "message": """test message
test TG-%s #%s ok
bye!
- """ % (user_story.ref, new_status.slug)},
- ]}
+ """ % (user_story.ref, new_status.slug),
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ }]
+ payload["total_commits_count"] = 1
mail.outbox = []
ev_hook = event_hooks.PushEventHook(user_story.project, payload)
@@ -320,6 +519,79 @@ def test_push_event_user_story_processing(client):
assert len(mail.outbox) == 1
+def test_push_event_issue_mention(client):
+ creation_status = f.IssueStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_issues"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ issue = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ take_snapshot(issue, user=creation_status.project.owner)
+ payload = deepcopy(push_base_payload)
+ payload["commits"] = [{
+ "message": """test message
+ test TG-%s ok
+ bye!
+ """ % (issue.ref),
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ }]
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(issue.project, payload)
+ ev_hook.process_event()
+ issue_history = get_history_queryset_by_model_instance(issue)
+ assert issue_history.count() == 1
+ assert issue_history[0].comment.startswith("This issue has been mentioned by")
+ assert len(mail.outbox) == 1
+
+
+def test_push_event_task_mention(client):
+ creation_status = f.TaskStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_tasks"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ take_snapshot(task, user=creation_status.project.owner)
+ payload = deepcopy(push_base_payload)
+ payload["commits"] = [{
+ "message": """test message
+ test TG-%s ok
+ bye!
+ """ % (task.ref),
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ }]
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(task.project, payload)
+ ev_hook.process_event()
+ task_history = get_history_queryset_by_model_instance(task)
+ assert task_history.count() == 1
+ assert task_history[0].comment.startswith("This task has been mentioned by")
+ assert len(mail.outbox) == 1
+
+
+def test_push_event_user_story_mention(client):
+ creation_status = f.UserStoryStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_us"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ user_story = f.UserStoryFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ take_snapshot(user_story, user=creation_status.project.owner)
+ payload = deepcopy(push_base_payload)
+ payload["commits"] = [{
+ "message": """test message
+ test TG-%s ok
+ bye!
+ """ % (user_story.ref),
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ }]
+
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(user_story.project, payload)
+ ev_hook.process_event()
+ us_history = get_history_queryset_by_model_instance(user_story)
+ assert us_history.count() == 1
+ assert us_history[0].comment.startswith("This user story has been mentioned by")
+ assert len(mail.outbox) == 1
+
+
def test_push_event_multiple_actions(client):
creation_status = f.IssueStatusFactory()
role = f.RoleFactory(project=creation_status.project, permissions=["view_issues"])
@@ -327,13 +599,17 @@ def test_push_event_multiple_actions(client):
new_status = f.IssueStatusFactory(project=creation_status.project)
issue1 = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
issue2 = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
- payload = {"commits": [
- {"message": """test message
+ payload = deepcopy(push_base_payload)
+ payload["commits"] = [{
+ "message": """test message
test TG-%s #%s ok
test TG-%s #%s ok
bye!
- """ % (issue1.ref, new_status.slug, issue2.ref, new_status.slug)},
- ]}
+ """ % (issue1.ref, new_status.slug, issue2.ref, new_status.slug),
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ }]
+ payload["total_commits_count"] = 1
mail.outbox = []
ev_hook1 = event_hooks.PushEventHook(issue1.project, payload)
ev_hook1.process_event()
@@ -350,12 +626,16 @@ def test_push_event_processing_case_insensitive(client):
f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
new_status = f.TaskStatusFactory(project=creation_status.project)
task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
- payload = {"commits": [
- {"message": """test message
+ payload = deepcopy(push_base_payload)
+ payload["commits"] = [{
+ "message": """test message
test tg-%s #%s ok
bye!
- """ % (task.ref, new_status.slug.upper())},
- ]}
+ """ % (task.ref, new_status.slug.upper()),
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ }]
+ payload["total_commits_count"] = 1
mail.outbox = []
ev_hook = event_hooks.PushEventHook(task.project, payload)
ev_hook.process_event()
@@ -366,12 +646,16 @@ def test_push_event_processing_case_insensitive(client):
def test_push_event_task_bad_processing_non_existing_ref(client):
issue_status = f.IssueStatusFactory()
- payload = {"commits": [
- {"message": """test message
+ payload = deepcopy(push_base_payload)
+ payload["commits"] = [{
+ "message": """test message
test TG-6666666 #%s ok
bye!
- """ % (issue_status.slug)},
- ]}
+ """ % (issue_status.slug),
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ }]
+ payload["total_commits_count"] = 1
mail.outbox = []
ev_hook = event_hooks.PushEventHook(issue_status.project, payload)
@@ -384,12 +668,16 @@ def test_push_event_task_bad_processing_non_existing_ref(client):
def test_push_event_us_bad_processing_non_existing_status(client):
user_story = f.UserStoryFactory.create()
- payload = {"commits": [
- {"message": """test message
+ payload = deepcopy(push_base_payload)
+ payload["commits"] = [{
+ "message": """test message
test TG-%s #non-existing-slug ok
bye!
- """ % (user_story.ref)},
- ]}
+ """ % (user_story.ref),
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ }]
+ payload["total_commits_count"] = 1
mail.outbox = []
@@ -403,12 +691,16 @@ def test_push_event_us_bad_processing_non_existing_status(client):
def test_push_event_bad_processing_non_existing_status(client):
issue = f.IssueFactory.create()
- payload = {"commits": [
- {"message": """test message
+ payload = deepcopy(push_base_payload)
+ payload["commits"] = [{
+ "message": """test message
test TG-%s #non-existing-slug ok
bye!
- """ % (issue.ref)},
- ]}
+ """ % (issue.ref),
+ "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ }]
+ payload["total_commits_count"] = 1
mail.outbox = []
@@ -432,15 +724,12 @@ def test_issues_event_opened_issue(client):
notify_policy.notify_level = NotifyLevel.all
notify_policy.save()
- payload = {
- "object_kind": "issue",
- "object_attributes": {
- "title": "test-title",
- "description": "test-body",
- "url": "http://gitlab.com/test/project/issues/11",
- "action": "open",
- },
- }
+ payload = deepcopy(new_issue_base_payload)
+ payload["object_attributes"]["title"] = "test-title"
+ payload["object_attributes"]["description"] = "test-body"
+ payload["object_attributes"]["url"] = "http://gitlab.com/test/project/issues/11"
+ payload["object_attributes"]["action"] = "open"
+ payload["repository"]["homepage"] = "test"
mail.outbox = []
@@ -459,15 +748,12 @@ def test_issues_event_other_than_opened_issue(client):
issue.project.default_priority = issue.priority
issue.project.save()
- payload = {
- "object_kind": "issue",
- "object_attributes": {
- "title": "test-title",
- "description": "test-body",
- "url": "http://gitlab.com/test/project/issues/11",
- "action": "update",
- },
- }
+ payload = deepcopy(new_issue_base_payload)
+ payload["object_attributes"]["title"] = "test-title"
+ payload["object_attributes"]["description"] = "test-body"
+ payload["object_attributes"]["url"] = "http://gitlab.com/test/project/issues/11"
+ payload["object_attributes"]["action"] = "update"
+ payload["repository"]["homepage"] = "test"
mail.outbox = []
@@ -486,12 +772,12 @@ def test_issues_event_bad_issue(client):
issue.project.default_priority = issue.priority
issue.project.save()
- payload = {
- "object_kind": "issue",
- "object_attributes": {
- "action": "open",
- },
- }
+ payload = deepcopy(new_issue_base_payload)
+ del payload["object_attributes"]["title"]
+ del payload["object_attributes"]["description"]
+ del payload["object_attributes"]["url"]
+ payload["object_attributes"]["action"] = "open"
+ payload["repository"]["homepage"] = "test"
mail.outbox = []
ev_hook = event_hooks.IssuesEventHook(issue.project, payload)
@@ -518,23 +804,13 @@ def test_issue_comment_event_on_existing_issue_task_and_us(client):
us = f.UserStoryFactory.create(external_reference=["gitlab", "http://gitlab.com/test/project/issues/11"], owner=project.owner, project=project)
take_snapshot(us, user=user)
- payload = {
- "user": {
- "username": "test"
- },
- "issue": {
- "iid": "11",
- "title": "test-title",
- },
- "object_attributes": {
- "noteable_type": "Issue",
- "note": "Test body",
- },
- "repository": {
- "homepage": "http://gitlab.com/test/project",
- },
- }
-
+ payload = deepcopy(issue_comment_base_payload)
+ payload["user"]["username"] = "test"
+ payload["issue"]["iid"] = "11"
+ payload["issue"]["title"] = "test-title"
+ payload["object_attributes"]["noteable_type"] = "Issue"
+ payload["object_attributes"]["note"] = "Test body"
+ payload["repository"]["homepage"] = "http://gitlab.com/test/project"
mail.outbox = []
@@ -568,22 +844,13 @@ def test_issue_comment_event_on_not_existing_issue_task_and_us(client):
us = f.UserStoryFactory.create(project=issue.project, external_reference=["gitlab", "10"])
take_snapshot(us, user=us.owner)
- payload = {
- "user": {
- "username": "test"
- },
- "issue": {
- "iid": "99999",
- "title": "test-title",
- },
- "object_attributes": {
- "noteable_type": "Issue",
- "note": "test comment",
- },
- "repository": {
- "homepage": "test",
- },
- }
+ payload = deepcopy(issue_comment_base_payload)
+ payload["user"]["username"] = "test"
+ payload["issue"]["iid"] = "99999"
+ payload["issue"]["title"] = "test-title"
+ payload["object_attributes"]["noteable_type"] = "Issue"
+ payload["object_attributes"]["note"] = "test comment"
+ payload["repository"]["homepage"] = "test"
mail.outbox = []
@@ -605,21 +872,14 @@ def test_issues_event_bad_comment(client):
issue = f.IssueFactory.create(external_reference=["gitlab", "10"])
take_snapshot(issue, user=issue.owner)
- payload = {
- "user": {
- "username": "test"
- },
- "issue": {
- "iid": "10",
- "title": "test-title",
- },
- "object_attributes": {
- "noteable_type": "Issue",
- },
- "repository": {
- "homepage": "test",
- },
- }
+ payload = deepcopy(issue_comment_base_payload)
+ payload["user"]["username"] = "test"
+ payload["issue"]["iid"] = "10"
+ payload["issue"]["title"] = "test-title"
+ payload["object_attributes"]["noteable_type"] = "Issue"
+ del payload["object_attributes"]["note"]
+ payload["repository"]["homepage"] = "test"
+
ev_hook = event_hooks.IssueCommentEventHook(issue.project, payload)
mail.outbox = []
@@ -671,9 +931,10 @@ def test_api_patch_project_modules(client):
def test_replace_gitlab_references():
- assert event_hooks.replace_gitlab_references("project-url", "#2") == "[GitLab#2](project-url/issues/2)"
- assert event_hooks.replace_gitlab_references("project-url", "#2 ") == "[GitLab#2](project-url/issues/2) "
- assert event_hooks.replace_gitlab_references("project-url", " #2 ") == " [GitLab#2](project-url/issues/2) "
- assert event_hooks.replace_gitlab_references("project-url", " #2") == " [GitLab#2](project-url/issues/2)"
- assert event_hooks.replace_gitlab_references("project-url", "#test") == "#test"
- assert event_hooks.replace_gitlab_references("project-url", None) == ""
+ ev_hook = event_hooks.BaseGitLabEventHook
+ assert ev_hook.replace_gitlab_references(None, "project-url", "#2") == "[GitLab#2](project-url/issues/2)"
+ assert ev_hook.replace_gitlab_references(None, "project-url", "#2 ") == "[GitLab#2](project-url/issues/2) "
+ assert ev_hook.replace_gitlab_references(None, "project-url", " #2 ") == " [GitLab#2](project-url/issues/2) "
+ assert ev_hook.replace_gitlab_references(None, "project-url", " #2") == " [GitLab#2](project-url/issues/2)"
+ assert ev_hook.replace_gitlab_references(None, "project-url", "#test") == "#test"
+ assert ev_hook.replace_gitlab_references(None, "project-url", None) == ""
diff --git a/tests/integration/test_hooks_gogs.py b/tests/integration/test_hooks_gogs.py
new file mode 100644
index 00000000..290bc3f8
--- /dev/null
+++ b/tests/integration/test_hooks_gogs.py
@@ -0,0 +1,502 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2014-2016 Andrey Antukh
+# Copyright (C) 2014-2016 Jesús Espino
+# Copyright (C) 2014-2016 David Barragán
+# Copyright (C) 2014-2016 Alejandro Alonso
+# Copyright (C) 2014-2016 Anler Hernández
+# 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 .
+
+import pytest
+
+from unittest import mock
+
+from django.core.urlresolvers import reverse
+from django.core import mail
+
+from taiga.base.utils import json
+from taiga.hooks.gogs import event_hooks
+from taiga.hooks.gogs.api import GogsViewSet
+from taiga.hooks.exceptions import ActionSyntaxException
+from taiga.projects import choices as project_choices
+from taiga.projects.issues.models import Issue
+from taiga.projects.tasks.models import Task
+from taiga.projects.userstories.models import UserStory
+from taiga.projects.models import Membership
+from taiga.projects.history.services import get_history_queryset_by_model_instance, take_snapshot
+from taiga.projects.notifications.choices import NotifyLevel
+from taiga.projects.notifications.models import NotifyPolicy
+from taiga.projects import services
+from .. import factories as f
+
+pytestmark = pytest.mark.django_db
+
+
+def test_bad_signature(client):
+ project = f.ProjectFactory()
+ url = reverse("gogs-hook-list")
+ url = "%s?project=%s" % (url, project.id)
+ data = {
+ "secret": "badbadbad"
+ }
+ response = client.post(url, json.dumps(data),
+ content_type="application/json")
+ response_content = response.data
+ assert response.status_code == 400
+ assert "Bad signature" in response_content["_error_message"]
+
+
+def test_ok_signature(client):
+ project = f.ProjectFactory()
+ f.ProjectModulesConfigFactory(project=project, config={
+ "gogs": {
+ "secret": "tpnIwJDz4e"
+ }
+ })
+
+ url = reverse("gogs-hook-list")
+ url = "%s?project=%s" % (url, project.id)
+ data = {"test:": "data", "secret": "tpnIwJDz4e"}
+ response = client.post(url, json.dumps(data),
+ content_type="application/json")
+
+ assert response.status_code == 204
+
+
+def test_blocked_project(client):
+ project = f.ProjectFactory(blocked_code=project_choices.BLOCKED_BY_STAFF)
+ f.ProjectModulesConfigFactory(project=project, config={
+ "gogs": {
+ "secret": "tpnIwJDz4e"
+ }
+ })
+
+ url = reverse("gogs-hook-list")
+ url = "%s?project=%s" % (url, project.id)
+ data = {"test:": "data", "secret": "tpnIwJDz4e"}
+ response = client.post(url, json.dumps(data),
+ content_type="application/json")
+
+ assert response.status_code == 451
+
+
+def test_push_event_detected(client):
+ project = f.ProjectFactory()
+ url = reverse("gogs-hook-list")
+ url = "%s?project=%s" % (url, project.id)
+ data = {
+ "commits": [
+ {
+ "message": "test message",
+ "author": {
+ "username": "test",
+ },
+ }
+ ],
+ "repository": {
+ "url": "http://test-url/test/project"
+ }
+ }
+
+ GogsViewSet._validate_signature = mock.Mock(return_value=True)
+
+ with mock.patch.object(event_hooks.PushEventHook, "process_event") as process_event_mock:
+ response = client.post(url, json.dumps(data),
+ HTTP_X_GITHUB_EVENT="push",
+ content_type="application/json")
+
+ assert process_event_mock.call_count == 1
+
+ assert response.status_code == 204
+
+
+def test_push_event_issue_processing(client):
+ creation_status = f.IssueStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_issues"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ new_status = f.IssueStatusFactory(project=creation_status.project)
+ issue = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ payload = {
+ "commits": [
+ {
+ "message": """test message
+ test TG-%s #%s ok
+ bye!
+ """ % (issue.ref, new_status.slug),
+ "author": {
+ "username": "test",
+ },
+ }
+ ],
+ "repository": {
+ "url": "http://test-url/test/project"
+ }
+ }
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(issue.project, payload)
+ ev_hook.process_event()
+ issue = Issue.objects.get(id=issue.id)
+ assert issue.status.id == new_status.id
+ assert len(mail.outbox) == 1
+
+
+def test_push_event_task_processing(client):
+ creation_status = f.TaskStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_tasks"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ new_status = f.TaskStatusFactory(project=creation_status.project)
+ task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ payload = {
+ "commits": [
+ {
+ "message": """test message
+ test TG-%s #%s ok
+ bye!
+ """ % (task.ref, new_status.slug),
+ "author": {
+ "username": "test",
+ },
+ }
+ ],
+ "repository": {
+ "url": "http://test-url/test/project"
+ }
+ }
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(task.project, payload)
+ ev_hook.process_event()
+ task = Task.objects.get(id=task.id)
+ assert task.status.id == new_status.id
+ assert len(mail.outbox) == 1
+
+
+def test_push_event_user_story_processing(client):
+ creation_status = f.UserStoryStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_us"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ new_status = f.UserStoryStatusFactory(project=creation_status.project)
+ user_story = f.UserStoryFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ payload = {
+ "commits": [
+ {
+ "message": """test message
+ test TG-%s #%s ok
+ bye!
+ """ % (user_story.ref, new_status.slug),
+ "author": {
+ "username": "test",
+ },
+ }
+ ],
+ "repository": {
+ "url": "http://test-url/test/project"
+ }
+ }
+
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(user_story.project, payload)
+ ev_hook.process_event()
+ user_story = UserStory.objects.get(id=user_story.id)
+ assert user_story.status.id == new_status.id
+ assert len(mail.outbox) == 1
+
+
+def test_push_event_issue_mention(client):
+ creation_status = f.IssueStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_issues"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ issue = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ take_snapshot(issue, user=creation_status.project.owner)
+ payload = {
+ "commits": [
+ {
+ "message": """test message
+ test TG-%s ok
+ bye!
+ """ % (issue.ref),
+ "author": {
+ "username": "test",
+ },
+ }
+ ],
+ "repository": {
+ "url": "http://test-url/test/project"
+ }
+ }
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(issue.project, payload)
+ ev_hook.process_event()
+ issue_history = get_history_queryset_by_model_instance(issue)
+ assert issue_history.count() == 1
+ assert issue_history[0].comment.startswith("This issue has been mentioned by")
+ assert len(mail.outbox) == 1
+
+
+def test_push_event_task_mention(client):
+ creation_status = f.TaskStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_tasks"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ take_snapshot(task, user=creation_status.project.owner)
+ payload = {
+ "commits": [
+ {
+ "message": """test message
+ test TG-%s ok
+ bye!
+ """ % (task.ref),
+ "author": {
+ "username": "test",
+ },
+ }
+ ],
+ "repository": {
+ "url": "http://test-url/test/project"
+ }
+ }
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(task.project, payload)
+ ev_hook.process_event()
+ task_history = get_history_queryset_by_model_instance(task)
+ assert task_history.count() == 1
+ assert task_history[0].comment.startswith("This task has been mentioned by")
+ assert len(mail.outbox) == 1
+
+
+def test_push_event_user_story_mention(client):
+ creation_status = f.UserStoryStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_us"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ user_story = f.UserStoryFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ take_snapshot(user_story, user=creation_status.project.owner)
+ payload = {
+ "commits": [
+ {
+ "message": """test message
+ test TG-%s ok
+ bye!
+ """ % (user_story.ref),
+ "author": {
+ "username": "test",
+ },
+ }
+ ],
+ "repository": {
+ "url": "http://test-url/test/project"
+ }
+ }
+
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(user_story.project, payload)
+ ev_hook.process_event()
+ us_history = get_history_queryset_by_model_instance(user_story)
+ assert us_history.count() == 1
+ assert us_history[0].comment.startswith("This user story has been mentioned by")
+ assert len(mail.outbox) == 1
+
+
+def test_push_event_multiple_actions(client):
+ creation_status = f.IssueStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_issues"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ new_status = f.IssueStatusFactory(project=creation_status.project)
+ issue1 = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ issue2 = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ payload = {
+ "commits": [
+ {
+ "message": """test message
+ test TG-%s #%s ok
+ test TG-%s #%s ok
+ bye!
+ """ % (issue1.ref, new_status.slug, issue2.ref, new_status.slug),
+ "author": {
+ "username": "test",
+ },
+ }
+ ],
+ "repository": {
+ "url": "http://test-url/test/project"
+ }
+ }
+ mail.outbox = []
+ ev_hook1 = event_hooks.PushEventHook(issue1.project, payload)
+ ev_hook1.process_event()
+ issue1 = Issue.objects.get(id=issue1.id)
+ issue2 = Issue.objects.get(id=issue2.id)
+ assert issue1.status.id == new_status.id
+ assert issue2.status.id == new_status.id
+ assert len(mail.outbox) == 2
+
+
+def test_push_event_processing_case_insensitive(client):
+ creation_status = f.TaskStatusFactory()
+ role = f.RoleFactory(project=creation_status.project, permissions=["view_tasks"])
+ f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
+ new_status = f.TaskStatusFactory(project=creation_status.project)
+ task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
+ payload = {
+ "commits": [
+ {
+ "message": """test message
+ test tg-%s #%s ok
+ bye!
+ """ % (task.ref, new_status.slug.upper()),
+ "author": {
+ "username": "test",
+ },
+ }
+ ],
+ "repository": {
+ "url": "http://test-url/test/project"
+ }
+ }
+ mail.outbox = []
+ ev_hook = event_hooks.PushEventHook(task.project, payload)
+ ev_hook.process_event()
+ task = Task.objects.get(id=task.id)
+ assert task.status.id == new_status.id
+ assert len(mail.outbox) == 1
+
+
+def test_push_event_task_bad_processing_non_existing_ref(client):
+ issue_status = f.IssueStatusFactory()
+ payload = {
+ "commits": [
+ {
+ "message": """test message
+ test TG-6666666 #%s ok
+ bye!
+ """ % (issue_status.slug),
+ "author": {
+ "username": "test",
+ },
+ }
+ ],
+ "repository": {
+ "url": "http://test-url/test/project"
+ }
+ }
+ mail.outbox = []
+
+ ev_hook = event_hooks.PushEventHook(issue_status.project, payload)
+ with pytest.raises(ActionSyntaxException) as excinfo:
+ ev_hook.process_event()
+
+ assert str(excinfo.value) == "The referenced element doesn't exist"
+ assert len(mail.outbox) == 0
+
+
+def test_push_event_us_bad_processing_non_existing_status(client):
+ user_story = f.UserStoryFactory.create()
+ payload = {
+ "commits": [
+ {
+ "message": """test message
+ test TG-%s #non-existing-slug ok
+ bye!
+ """ % (user_story.ref),
+ "author": {
+ "username": "test",
+ },
+ }
+ ],
+ "repository": {
+ "url": "http://test-url/test/project"
+ }
+ }
+
+ mail.outbox = []
+
+ ev_hook = event_hooks.PushEventHook(user_story.project, payload)
+ with pytest.raises(ActionSyntaxException) as excinfo:
+ ev_hook.process_event()
+
+ assert str(excinfo.value) == "The status doesn't exist"
+ assert len(mail.outbox) == 0
+
+
+def test_push_event_bad_processing_non_existing_status(client):
+ issue = f.IssueFactory.create()
+ payload = {
+ "commits": [
+ {
+ "message": """test message
+ test TG-%s #non-existing-slug ok
+ bye!
+ """ % (issue.ref),
+ "author": {
+ "username": "test",
+ },
+ }
+ ],
+ "repository": {
+ "url": "http://test-url/test/project"
+ }
+ }
+
+ mail.outbox = []
+
+ ev_hook = event_hooks.PushEventHook(issue.project, payload)
+ with pytest.raises(ActionSyntaxException) as excinfo:
+ ev_hook.process_event()
+
+ assert str(excinfo.value) == "The status doesn't exist"
+ assert len(mail.outbox) == 0
+
+
+def test_api_get_project_modules(client):
+ project = f.create_project()
+ f.MembershipFactory(project=project, user=project.owner, is_admin=True)
+
+ url = reverse("projects-modules", args=(project.id,))
+
+ client.login(project.owner)
+ response = client.get(url)
+ assert response.status_code == 200
+ content = response.data
+ assert "gogs" in content
+ assert content["gogs"]["secret"] != ""
+ assert content["gogs"]["webhooks_url"] != ""
+
+
+def test_api_patch_project_modules(client):
+ project = f.create_project()
+ f.MembershipFactory(project=project, user=project.owner, is_admin=True)
+
+ url = reverse("projects-modules", args=(project.id,))
+
+ client.login(project.owner)
+ data = {
+ "gogs": {
+ "secret": "test_secret",
+ "url": "test_url",
+ }
+ }
+ response = client.patch(url, json.dumps(data), content_type="application/json")
+ assert response.status_code == 204
+
+ config = services.get_modules_config(project).config
+ assert "gogs" in config
+ assert config["gogs"]["secret"] == "test_secret"
+ assert config["gogs"]["webhooks_url"] != "test_url"
+
+
+def test_replace_gogs_references():
+ ev_hook = event_hooks.BaseGogsEventHook
+ assert ev_hook.replace_gogs_references(None, "project-url", "#2") == "[Gogs#2](project-url/issues/2)"
+ assert ev_hook.replace_gogs_references(None, "project-url", "#2 ") == "[Gogs#2](project-url/issues/2) "
+ assert ev_hook.replace_gogs_references(None, "project-url", " #2 ") == " [Gogs#2](project-url/issues/2) "
+ assert ev_hook.replace_gogs_references(None, "project-url", " #2") == " [Gogs#2](project-url/issues/2)"
+ assert ev_hook.replace_gogs_references(None, "project-url", "#test") == "#test"
+ assert ev_hook.replace_gogs_references(None, "project-url", None) == ""