Merge pull request #782 from taigaio/hooks-refactor

Refactor Github/Gitlab/Bitbucket hooks
remotes/origin/issue/4795/notification_even_they_are_disabled
David Barragán Merino 2016-07-28 14:43:03 +02:00 committed by GitHub
commit 4c84aaa885
23 changed files with 1710 additions and 679 deletions

View File

@ -24,6 +24,11 @@
- Gzip export/import support. - Gzip export/import support.
- Export performance improvements. - Export performance improvements.
- Add filter by email domain registration and invitation by setting. - 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 ### Misc
- [API] Improve performance of some calls over list. - [API] Improve performance of some calls over list.

View File

@ -313,6 +313,7 @@ INSTALLED_APPS = [
"taiga.hooks.github", "taiga.hooks.github",
"taiga.hooks.gitlab", "taiga.hooks.gitlab",
"taiga.hooks.bitbucket", "taiga.hooks.bitbucket",
"taiga.hooks.gogs",
"taiga.webhooks", "taiga.webhooks",
"djmail", "djmail",
@ -506,6 +507,7 @@ PROJECT_MODULES_CONFIGURATORS = {
"github": "taiga.hooks.github.services.get_or_generate_config", "github": "taiga.hooks.github.services.get_or_generate_config",
"gitlab": "taiga.hooks.gitlab.services.get_or_generate_config", "gitlab": "taiga.hooks.gitlab.services.get_or_generate_config",
"bitbucket": "taiga.hooks.bitbucket.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"] BITBUCKET_VALID_ORIGIN_IPS = ["131.103.20.165", "131.103.20.166", "104.192.143.192/28", "104.192.143.208/28"]

View File

@ -72,13 +72,5 @@ class BitBucketViewSet(BaseWebhookApiViewSet):
return project_secret == secret_key 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): def _get_event_name(self, request):
return request.META.get('HTTP_X_EVENT_KEY', None) return request.META.get('HTTP_X_EVENT_KEY', None)

View File

@ -18,181 +18,67 @@
import re import re
from django.utils.translation import ugettext as _ from taiga.hooks.event_hooks import BaseNewIssueEventHook, BaseIssueCommentEventHook, BasePushEventHook
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
class PushEventHook(BaseEventHook): class BaseBitBucketEventHook():
def process_event(self): platform = "BitBucket"
if self.payload is None: platform_slug = "bitbucket"
return
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', []) changes = self.payload.get("push", {}).get('changes', [])
for change in filter(None, changes): for change in filter(None, changes):
commits = change.get("commits", []) for commit in change.get("commits", []):
if not commits: message = commit.get("message")
continue result.append({
'user_id': commit.get('author', {}).get('user', {}).get('uuid', None),
for commit in commits: "user_name": commit.get('author', {}).get('user', {}).get('username', None),
message = commit.get("message", None) "user_url": commit.get('author', {}).get('user', {}).get('links', {}).get('html', {}).get('href'),
if not message: "commit_id": commit.get("hash", None),
continue "commit_url": commit.get("links", {}).get('html', {}).get('href'),
"commit_message": message.strip(),
self._process_message(message, None) })
return result
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)

View File

@ -16,11 +16,247 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
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: class BaseEventHook:
platform = "Unknown"
platform_slug = "unknown"
def __init__(self, project, payload): def __init__(self, project, payload):
self.project = project self.project = project
self.payload = payload 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): 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)

View File

@ -16,201 +16,72 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
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 import re
from taiga.hooks.event_hooks import BaseNewIssueEventHook, BaseIssueCommentEventHook, BasePushEventHook
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)
def replace_github_references(project_url, wiki_text): class BaseGitHubEventHook():
if wiki_text == None: platform = "GitHub"
wiki_text = "" platform_slug = "github"
template = "\g<1>[GitHub#\g<2>]({}/issues/\g<2>)\g<3>".format(project_url) def replace_github_references(self, project_url, wiki_text):
return re.sub(r"(\s|^)#(\d+)(\s|$)", template, wiki_text, 0, re.M) 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): class IssuesEventHook(BaseGitHubEventHook, BaseNewIssueEventHook):
def process_event(self): def ignore(self):
if self.payload.get('action', None) != "opened": return self.payload.get('action', None) != "opened"
return
number = self.payload.get('issue', {}).get('number', None) def get_data(self):
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)
description = self.payload.get('issue', {}).get('body', None) 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) 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 = 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]): class PushEventHook(BaseGitHubEventHook, BasePushEventHook):
raise ActionSyntaxException(_("Invalid issue comment information")) 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]) return result
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)

View File

@ -18,10 +18,8 @@
import uuid import uuid
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from taiga.users.models import AuthData
from taiga.base.utils.urls import get_absolute_url 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) url = "%s?project=%s" % (url, project.id)
g_config["webhooks_url"] = url g_config["webhooks_url"] = url
return g_config 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

View File

@ -70,14 +70,6 @@ class GitLabViewSet(BaseWebhookApiViewSet):
return project_secret == secret_key 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): def _get_event_name(self, request):
payload = json.loads(request.body.decode("utf-8")) payload = json.loads(request.body.decode("utf-8"))
return payload.get('object_kind', 'push') if payload is not None else 'empty' return payload.get('object_kind', 'push') if payload is not None else 'empty'

View File

@ -19,158 +19,71 @@
import re import re
import os import os
from django.utils.translation import ugettext as _ from taiga.hooks.event_hooks import BaseNewIssueEventHook, BaseIssueCommentEventHook, BasePushEventHook
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
class PushEventHook(BaseEventHook): class BaseGitLabEventHook():
def process_event(self): platform = "GitLab"
if self.payload is None: platform_slug = "gitlab"
return
commits = self.payload.get("commits", []) def replace_gitlab_references(self, project_url, wiki_text):
for commit in commits: if wiki_text is None:
message = commit.get("message", None) wiki_text = ""
self._process_message(message, None)
def _process_message(self, message, gitlab_user): 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)
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)
def replace_gitlab_references(project_url, wiki_text): class IssuesEventHook(BaseGitLabEventHook, BaseNewIssueEventHook):
if wiki_text is None: def ignore(self):
wiki_text = "" return self.payload.get('object_attributes', {}).get("action", "") != "open"
template = "\g<1>[GitLab#\g<2>]({}/issues/\g<2>)\g<3>".format(project_url) def get_data(self):
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)
description = self.payload.get('object_attributes', {}).get('description', None) 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) 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 = 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]): class PushEventHook(BaseGitLabEventHook, BasePushEventHook):
raise ActionSyntaxException(_("Invalid issue comment information")) def get_data(self):
result = []
issues = Issue.objects.filter(external_reference=["gitlab", gitlab_url]) for commit in self.payload.get("commits", []):
tasks = Task.objects.filter(external_reference=["gitlab", gitlab_url]) user_name = commit.get('author', {}).get('name', None)
uss = UserStory.objects.filter(external_reference=["gitlab", gitlab_url]) result.append({
"user_id": None,
for item in list(issues) + list(tasks) + list(uss): "user_name": user_name,
if number and subject and gitlab_user_name and gitlab_user_url: "user_url": None,
comment = _("Comment by [@{gitlab_user_name}]({gitlab_user_url} " "commit_id": commit.get("id", None),
"\"See @{gitlab_user_name}'s GitLab profile\") " "commit_url": commit.get("url", None),
"from GitLab.\nOrigin GitLab issue: [gl#{number} - {subject}]({gitlab_url} " "commit_message": commit.get("message").strip(),
"\"Go to 'gl#{number} - {subject}'\")\n\n" })
"{message}").format(gitlab_user_name=gitlab_user_name, return result
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)

View File

@ -18,7 +18,6 @@
import uuid import uuid
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.conf import settings 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"]) url = "{}?project={}&key={}".format(url, project.id, g_config["secret"])
g_config["webhooks_url"] = url g_config["webhooks_url"] = url
return g_config 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

View File

44
taiga/hooks/gogs/api.py Normal file
View File

@ -0,0 +1,44 @@
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from 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"

View File

@ -0,0 +1,52 @@
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import 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

View File

@ -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),
]

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@ -0,0 +1 @@
# This file is needed to load migrations

View File

@ -0,0 +1,37 @@
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import 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

View File

@ -208,6 +208,12 @@ from taiga.hooks.bitbucket.api import BitBucketViewSet
router.register(r"bitbucket-hook", BitBucketViewSet, base_name="bitbucket-hook") 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 # Importer
from taiga.export_import.api import ProjectImporterViewSet, ProjectExporterViewSet from taiga.export_import.api import ProjectImporterViewSet, ProjectExporterViewSet

View File

@ -246,6 +246,13 @@ def test_push_event_issue_processing(client):
new_status = f.IssueStatusFactory(project=creation_status.project) new_status = f.IssueStatusFactory(project=creation_status.project)
issue = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner) issue = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = { payload = {
"actor": {
"user": {
"uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
"username": "test-user",
"links": {"html": {"href": "http://bitbucket.com/test-user"}}
}
},
"push": { "push": {
"changes": [ "changes": [
{ {
@ -271,6 +278,13 @@ def test_push_event_task_processing(client):
new_status = f.TaskStatusFactory(project=creation_status.project) new_status = f.TaskStatusFactory(project=creation_status.project)
task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner) task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = { payload = {
"actor": {
"user": {
"uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
"username": "test-user",
"links": {"html": {"href": "http://bitbucket.com/test-user"}}
}
},
"push": { "push": {
"changes": [ "changes": [
{ {
@ -296,6 +310,13 @@ def test_push_event_user_story_processing(client):
new_status = f.UserStoryStatusFactory(project=creation_status.project) 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) user_story = f.UserStoryFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = { payload = {
"actor": {
"user": {
"uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
"username": "test-user",
"links": {"html": {"href": "http://bitbucket.com/test-user"}}
}
},
"push": { "push": {
"changes": [ "changes": [
{ {
@ -314,6 +335,108 @@ def test_push_event_user_story_processing(client):
assert len(mail.outbox) == 1 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): def test_push_event_multiple_actions(client):
creation_status = f.IssueStatusFactory() creation_status = f.IssueStatusFactory()
role = f.RoleFactory(project=creation_status.project, permissions=["view_issues"]) 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) 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) issue2 = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = { payload = {
"actor": {
"user": {
"uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
"username": "test-user",
"links": {"html": {"href": "http://bitbucket.com/test-user"}}
}
},
"push": { "push": {
"changes": [ "changes": [
{ {
@ -349,6 +479,13 @@ def test_push_event_processing_case_insensitive(client):
new_status = f.TaskStatusFactory(project=creation_status.project) new_status = f.TaskStatusFactory(project=creation_status.project)
task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner) task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = { payload = {
"actor": {
"user": {
"uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
"username": "test-user",
"links": {"html": {"href": "http://bitbucket.com/test-user"}}
}
},
"push": { "push": {
"changes": [ "changes": [
{ {
@ -370,6 +507,13 @@ def test_push_event_processing_case_insensitive(client):
def test_push_event_task_bad_processing_non_existing_ref(client): def test_push_event_task_bad_processing_non_existing_ref(client):
issue_status = f.IssueStatusFactory() issue_status = f.IssueStatusFactory()
payload = { payload = {
"actor": {
"user": {
"uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
"username": "test-user",
"links": {"html": {"href": "http://bitbucket.com/test-user"}}
}
},
"push": { "push": {
"changes": [ "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): def test_push_event_us_bad_processing_non_existing_status(client):
user_story = f.UserStoryFactory.create() user_story = f.UserStoryFactory.create()
payload = { payload = {
"actor": {
"user": {
"uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
"username": "test-user",
"links": {"html": {"href": "http://bitbucket.com/test-user"}}
}
},
"push": { "push": {
"changes": [ "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): def test_push_event_bad_processing_non_existing_status(client):
issue = f.IssueFactory.create() issue = f.IssueFactory.create()
payload = { payload = {
"actor": {
"user": {
"uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
"username": "test-user",
"links": {"html": {"href": "http://bitbucket.com/test-user"}}
}
},
"push": { "push": {
"changes": [ "changes": [
{ {
@ -686,8 +844,10 @@ def test_api_patch_project_modules(client):
def test_replace_bitbucket_references(): def test_replace_bitbucket_references():
assert event_hooks.replace_bitbucket_references("project-url", "#2") == "[BitBucket#2](project-url/issues/2)" ev_hook = event_hooks.BaseBitBucketEventHook
assert event_hooks.replace_bitbucket_references("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 event_hooks.replace_bitbucket_references("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 event_hooks.replace_bitbucket_references("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 event_hooks.replace_bitbucket_references("project-url", "#test") == "#test" 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) == ""

View File

@ -172,6 +172,70 @@ def test_push_event_user_story_processing(client):
assert len(mail.outbox) == 1 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): def test_push_event_multiple_actions(client):
creation_status = f.IssueStatusFactory() creation_status = f.IssueStatusFactory()
role = f.RoleFactory(project=creation_status.project, permissions=["view_issues"]) 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) take_snapshot(issue, user=issue.owner)
payload = { payload = {
"action": "other", "action": "created",
"issue": {}, "issue": {},
"comment": {}, "comment": {},
"repository": { "repository": {
@ -512,9 +576,10 @@ def test_api_patch_project_modules(client):
def test_replace_github_references(): def test_replace_github_references():
assert event_hooks.replace_github_references("project-url", "#2") == "[GitHub#2](project-url/issues/2)" ev_hook = event_hooks.BaseGitHubEventHook
assert event_hooks.replace_github_references("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 event_hooks.replace_github_references("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 event_hooks.replace_github_references("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 event_hooks.replace_github_references("project-url", "#test") == "#test" assert ev_hook.replace_github_references(None, "project-url", " #2") == " [GitHub#2](project-url/issues/2)"
assert event_hooks.replace_github_references("project-url", None) == "" assert ev_hook.replace_github_references(None, "project-url", "#test") == "#test"
assert ev_hook.replace_github_references(None, "project-url", None) == ""

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest import pytest
from copy import deepcopy
from unittest import mock from unittest import mock
@ -41,6 +42,189 @@ from .. import factories as f
pytestmark = pytest.mark.django_db 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): def test_bad_signature(client):
project = f.ProjectFactory() project = f.ProjectFactory()
@ -90,8 +274,8 @@ def test_ok_empty_payload(client):
url = reverse("gitlab-hook-list") url = reverse("gitlab-hook-list")
url = "{}?project={}&key={}".format(url, project.id, "tpnIwJDz4e") url = "{}?project={}&key={}".format(url, project.id, "tpnIwJDz4e")
data = {} response = client.post(url, "null", content_type="application/json",
response = client.post(url,"null", content_type="application/json", REMOTE_ADDR="111.111.111.111") REMOTE_ADDR="111.111.111.111")
assert response.status_code == 204 assert response.status_code == 204
@ -108,8 +292,7 @@ def test_ok_signature_ip_in_network(client):
url = reverse("gitlab-hook-list") url = reverse("gitlab-hook-list")
url = "{}?project={}&key={}".format(url, project.id, "tpnIwJDz4e") url = "{}?project={}&key={}".format(url, project.id, "tpnIwJDz4e")
data = {"test:": "data"} data = {"test:": "data"}
response = client.post(url, response = client.post(url, json.dumps(data),
json.dumps(data),
content_type="application/json", content_type="application/json",
REMOTE_ADDR="111.111.111.112") REMOTE_ADDR="111.111.111.112")
@ -243,9 +426,13 @@ def test_push_event_detected(client):
project = f.ProjectFactory() project = f.ProjectFactory()
url = reverse("gitlab-hook-list") url = reverse("gitlab-hook-list")
url = "%s?project=%s" % (url, project.id) url = "%s?project=%s" % (url, project.id)
data = {"commits": [ data = deepcopy(push_base_payload)
{"message": "test message"}, 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) 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) f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
new_status = f.IssueStatusFactory(project=creation_status.project) new_status = f.IssueStatusFactory(project=creation_status.project)
issue = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner) issue = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = {"commits": [ payload = deepcopy(push_base_payload)
{"message": """test message payload["commits"] = [{
test TG-%s #%s ok "message": """test message
bye! test TG-%s #%s ok
""" % (issue.ref, new_status.slug)}, bye!
]} """ % (issue.ref, new_status.slug),
"id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
}]
payload["total_commits_count"] = 1
mail.outbox = [] mail.outbox = []
ev_hook = event_hooks.PushEventHook(issue.project, payload) ev_hook = event_hooks.PushEventHook(issue.project, payload)
ev_hook.process_event() 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) f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
new_status = f.TaskStatusFactory(project=creation_status.project) new_status = f.TaskStatusFactory(project=creation_status.project)
task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner) task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = {"commits": [ payload = deepcopy(push_base_payload)
{"message": """test message payload["commits"] = [{
"message": """test message
test TG-%s #%s ok test TG-%s #%s ok
bye! 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 = [] mail.outbox = []
ev_hook = event_hooks.PushEventHook(task.project, payload) ev_hook = event_hooks.PushEventHook(task.project, payload)
ev_hook.process_event() 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) f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
new_status = f.UserStoryStatusFactory(project=creation_status.project) 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) user_story = f.UserStoryFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = {"commits": [ payload = deepcopy(push_base_payload)
{"message": """test message payload["commits"] = [{
"message": """test message
test TG-%s #%s ok test TG-%s #%s ok
bye! 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 = [] mail.outbox = []
ev_hook = event_hooks.PushEventHook(user_story.project, payload) 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 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): def test_push_event_multiple_actions(client):
creation_status = f.IssueStatusFactory() creation_status = f.IssueStatusFactory()
role = f.RoleFactory(project=creation_status.project, permissions=["view_issues"]) 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) new_status = f.IssueStatusFactory(project=creation_status.project)
issue1 = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner) 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) issue2 = f.IssueFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = {"commits": [ payload = deepcopy(push_base_payload)
{"message": """test message payload["commits"] = [{
"message": """test message
test TG-%s #%s ok test TG-%s #%s ok
test TG-%s #%s ok test TG-%s #%s ok
bye! 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 = [] mail.outbox = []
ev_hook1 = event_hooks.PushEventHook(issue1.project, payload) ev_hook1 = event_hooks.PushEventHook(issue1.project, payload)
ev_hook1.process_event() 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) f.MembershipFactory(project=creation_status.project, role=role, user=creation_status.project.owner)
new_status = f.TaskStatusFactory(project=creation_status.project) new_status = f.TaskStatusFactory(project=creation_status.project)
task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner) task = f.TaskFactory.create(status=creation_status, project=creation_status.project, owner=creation_status.project.owner)
payload = {"commits": [ payload = deepcopy(push_base_payload)
{"message": """test message payload["commits"] = [{
"message": """test message
test tg-%s #%s ok test tg-%s #%s ok
bye! 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 = [] mail.outbox = []
ev_hook = event_hooks.PushEventHook(task.project, payload) ev_hook = event_hooks.PushEventHook(task.project, payload)
ev_hook.process_event() 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): def test_push_event_task_bad_processing_non_existing_ref(client):
issue_status = f.IssueStatusFactory() issue_status = f.IssueStatusFactory()
payload = {"commits": [ payload = deepcopy(push_base_payload)
{"message": """test message payload["commits"] = [{
"message": """test message
test TG-6666666 #%s ok test TG-6666666 #%s ok
bye! bye!
""" % (issue_status.slug)}, """ % (issue_status.slug),
]} "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
}]
payload["total_commits_count"] = 1
mail.outbox = [] mail.outbox = []
ev_hook = event_hooks.PushEventHook(issue_status.project, payload) 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): def test_push_event_us_bad_processing_non_existing_status(client):
user_story = f.UserStoryFactory.create() user_story = f.UserStoryFactory.create()
payload = {"commits": [ payload = deepcopy(push_base_payload)
{"message": """test message payload["commits"] = [{
"message": """test message
test TG-%s #non-existing-slug ok test TG-%s #non-existing-slug ok
bye! bye!
""" % (user_story.ref)}, """ % (user_story.ref),
]} "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
}]
payload["total_commits_count"] = 1
mail.outbox = [] 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): def test_push_event_bad_processing_non_existing_status(client):
issue = f.IssueFactory.create() issue = f.IssueFactory.create()
payload = {"commits": [ payload = deepcopy(push_base_payload)
{"message": """test message payload["commits"] = [{
"message": """test message
test TG-%s #non-existing-slug ok test TG-%s #non-existing-slug ok
bye! bye!
""" % (issue.ref)}, """ % (issue.ref),
]} "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
}]
payload["total_commits_count"] = 1
mail.outbox = [] mail.outbox = []
@ -432,15 +724,12 @@ def test_issues_event_opened_issue(client):
notify_policy.notify_level = NotifyLevel.all notify_policy.notify_level = NotifyLevel.all
notify_policy.save() notify_policy.save()
payload = { payload = deepcopy(new_issue_base_payload)
"object_kind": "issue", payload["object_attributes"]["title"] = "test-title"
"object_attributes": { payload["object_attributes"]["description"] = "test-body"
"title": "test-title", payload["object_attributes"]["url"] = "http://gitlab.com/test/project/issues/11"
"description": "test-body", payload["object_attributes"]["action"] = "open"
"url": "http://gitlab.com/test/project/issues/11", payload["repository"]["homepage"] = "test"
"action": "open",
},
}
mail.outbox = [] mail.outbox = []
@ -459,15 +748,12 @@ def test_issues_event_other_than_opened_issue(client):
issue.project.default_priority = issue.priority issue.project.default_priority = issue.priority
issue.project.save() issue.project.save()
payload = { payload = deepcopy(new_issue_base_payload)
"object_kind": "issue", payload["object_attributes"]["title"] = "test-title"
"object_attributes": { payload["object_attributes"]["description"] = "test-body"
"title": "test-title", payload["object_attributes"]["url"] = "http://gitlab.com/test/project/issues/11"
"description": "test-body", payload["object_attributes"]["action"] = "update"
"url": "http://gitlab.com/test/project/issues/11", payload["repository"]["homepage"] = "test"
"action": "update",
},
}
mail.outbox = [] mail.outbox = []
@ -486,12 +772,12 @@ def test_issues_event_bad_issue(client):
issue.project.default_priority = issue.priority issue.project.default_priority = issue.priority
issue.project.save() issue.project.save()
payload = { payload = deepcopy(new_issue_base_payload)
"object_kind": "issue", del payload["object_attributes"]["title"]
"object_attributes": { del payload["object_attributes"]["description"]
"action": "open", del payload["object_attributes"]["url"]
}, payload["object_attributes"]["action"] = "open"
} payload["repository"]["homepage"] = "test"
mail.outbox = [] mail.outbox = []
ev_hook = event_hooks.IssuesEventHook(issue.project, payload) 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) us = f.UserStoryFactory.create(external_reference=["gitlab", "http://gitlab.com/test/project/issues/11"], owner=project.owner, project=project)
take_snapshot(us, user=user) take_snapshot(us, user=user)
payload = { payload = deepcopy(issue_comment_base_payload)
"user": { payload["user"]["username"] = "test"
"username": "test" payload["issue"]["iid"] = "11"
}, payload["issue"]["title"] = "test-title"
"issue": { payload["object_attributes"]["noteable_type"] = "Issue"
"iid": "11", payload["object_attributes"]["note"] = "Test body"
"title": "test-title", payload["repository"]["homepage"] = "http://gitlab.com/test/project"
},
"object_attributes": {
"noteable_type": "Issue",
"note": "Test body",
},
"repository": {
"homepage": "http://gitlab.com/test/project",
},
}
mail.outbox = [] 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"]) us = f.UserStoryFactory.create(project=issue.project, external_reference=["gitlab", "10"])
take_snapshot(us, user=us.owner) take_snapshot(us, user=us.owner)
payload = { payload = deepcopy(issue_comment_base_payload)
"user": { payload["user"]["username"] = "test"
"username": "test" payload["issue"]["iid"] = "99999"
}, payload["issue"]["title"] = "test-title"
"issue": { payload["object_attributes"]["noteable_type"] = "Issue"
"iid": "99999", payload["object_attributes"]["note"] = "test comment"
"title": "test-title", payload["repository"]["homepage"] = "test"
},
"object_attributes": {
"noteable_type": "Issue",
"note": "test comment",
},
"repository": {
"homepage": "test",
},
}
mail.outbox = [] mail.outbox = []
@ -605,21 +872,14 @@ def test_issues_event_bad_comment(client):
issue = f.IssueFactory.create(external_reference=["gitlab", "10"]) issue = f.IssueFactory.create(external_reference=["gitlab", "10"])
take_snapshot(issue, user=issue.owner) take_snapshot(issue, user=issue.owner)
payload = { payload = deepcopy(issue_comment_base_payload)
"user": { payload["user"]["username"] = "test"
"username": "test" payload["issue"]["iid"] = "10"
}, payload["issue"]["title"] = "test-title"
"issue": { payload["object_attributes"]["noteable_type"] = "Issue"
"iid": "10", del payload["object_attributes"]["note"]
"title": "test-title", payload["repository"]["homepage"] = "test"
},
"object_attributes": {
"noteable_type": "Issue",
},
"repository": {
"homepage": "test",
},
}
ev_hook = event_hooks.IssueCommentEventHook(issue.project, payload) ev_hook = event_hooks.IssueCommentEventHook(issue.project, payload)
mail.outbox = [] mail.outbox = []
@ -671,9 +931,10 @@ def test_api_patch_project_modules(client):
def test_replace_gitlab_references(): def test_replace_gitlab_references():
assert event_hooks.replace_gitlab_references("project-url", "#2") == "[GitLab#2](project-url/issues/2)" ev_hook = event_hooks.BaseGitLabEventHook
assert event_hooks.replace_gitlab_references("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 event_hooks.replace_gitlab_references("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 event_hooks.replace_gitlab_references("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 event_hooks.replace_gitlab_references("project-url", "#test") == "#test" assert ev_hook.replace_gitlab_references(None, "project-url", " #2") == " [GitLab#2](project-url/issues/2)"
assert event_hooks.replace_gitlab_references("project-url", None) == "" assert ev_hook.replace_gitlab_references(None, "project-url", "#test") == "#test"
assert ev_hook.replace_gitlab_references(None, "project-url", None) == ""

View File

@ -0,0 +1,502 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# Copyright (C) 2014-2016 Anler Hernández <hello@anler.me>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
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) == ""