Merge pull request #382 from taigaio/issue/2981/bitbucket-webhooks-fix
Issue/2981/bitbucket webhooks fixremotes/origin/enhancement/email-actions
commit
485799918e
|
@ -7,6 +7,7 @@
|
||||||
- Add a "field type" property for custom fields: 'text' and 'multiline text' right now (thanks to [@artlepool](https://github.com/artlepool))
|
- Add a "field type" property for custom fields: 'text' and 'multiline text' right now (thanks to [@artlepool](https://github.com/artlepool))
|
||||||
- Allow multiple actions in the commit messages.
|
- Allow multiple actions in the commit messages.
|
||||||
- Now every user that coments USs, Issues or Tasks will be involved in it (add author to the watchers list).
|
- Now every user that coments USs, Issues or Tasks will be involved in it (add author to the watchers list).
|
||||||
|
- Fix the compatibility with BitBucket webhooks and add issues and issues comments integration.
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
- API: Mixin fields 'users', 'members' and 'memberships' in ProjectDetailSerializer
|
- API: Mixin fields 'users', 'members' and 'memberships' in ProjectDetailSerializer
|
||||||
|
|
|
@ -29,18 +29,11 @@ from ipware.ip import get_ip
|
||||||
|
|
||||||
class BitBucketViewSet(BaseWebhookApiViewSet):
|
class BitBucketViewSet(BaseWebhookApiViewSet):
|
||||||
event_hook_classes = {
|
event_hook_classes = {
|
||||||
"push": event_hooks.PushEventHook,
|
"repo:push": event_hooks.PushEventHook,
|
||||||
|
"issue:created": event_hooks.IssuesEventHook,
|
||||||
|
"issue:comment_created": event_hooks.IssueCommentEventHook,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_payload(self, request):
|
|
||||||
try:
|
|
||||||
body = parse_qs(request.body.decode("utf-8"), strict_parsing=True)
|
|
||||||
payload = body["payload"]
|
|
||||||
except (ValueError, KeyError):
|
|
||||||
raise exc.BadRequest(_("The payload is not a valid application/x-www-form-urlencoded"))
|
|
||||||
|
|
||||||
return payload
|
|
||||||
|
|
||||||
def _validate_signature(self, project, request):
|
def _validate_signature(self, project, request):
|
||||||
secret_key = request.GET.get("key", None)
|
secret_key = request.GET.get("key", None)
|
||||||
|
|
||||||
|
@ -75,4 +68,4 @@ class BitBucketViewSet(BaseWebhookApiViewSet):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_event_name(self, request):
|
def _get_event_name(self, request):
|
||||||
return "push"
|
return request.META.get('HTTP_X_EVENT_KEY', None)
|
||||||
|
|
|
@ -37,17 +37,10 @@ class PushEventHook(BaseEventHook):
|
||||||
if self.payload is None:
|
if self.payload is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# In bitbucket the payload is a list! :(
|
changes = self.payload.get("push", {}).get('changes', [])
|
||||||
for payload_element_text in self.payload:
|
for change in changes:
|
||||||
try:
|
message = change.get("new", {}).get("target", {}).get("message", None)
|
||||||
payload_element = json.loads(payload_element_text)
|
self._process_message(message, None)
|
||||||
except ValueError:
|
|
||||||
raise exc.BadRequest(_("The payload is not valid"))
|
|
||||||
|
|
||||||
commits = payload_element.get("commits", [])
|
|
||||||
for commit in commits:
|
|
||||||
message = commit.get("message", None)
|
|
||||||
self._process_message(message, None)
|
|
||||||
|
|
||||||
def _process_message(self, message, bitbucket_user):
|
def _process_message(self, message, bitbucket_user):
|
||||||
"""
|
"""
|
||||||
|
@ -98,3 +91,98 @@ class PushEventHook(BaseEventHook):
|
||||||
def replace_bitbucket_references(project_url, wiki_text):
|
def replace_bitbucket_references(project_url, wiki_text):
|
||||||
template = "\g<1>[BitBucket#\g<2>]({}/issues/\g<2>)\g<3>".format(project_url)
|
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)
|
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: [gh#{number} - {subject}]({bitbucket_url} "
|
||||||
|
"\"Go to 'gh#{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: [gh#{number} - {subject}]({bitbucket_url} "
|
||||||
|
"\"Go to 'gh#{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)
|
||||||
|
|
|
@ -40,16 +40,5 @@ def get_or_generate_config(project):
|
||||||
return g_config
|
return g_config
|
||||||
|
|
||||||
|
|
||||||
def get_bitbucket_user(user_email):
|
def get_bitbucket_user(user_id):
|
||||||
user = None
|
return User.objects.get(is_system=True, username__startswith="bitbucket")
|
||||||
|
|
||||||
if user_email:
|
|
||||||
try:
|
|
||||||
user = User.objects.get(email=user_email)
|
|
||||||
except User.DoesNotExist:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
user = User.objects.get(is_system=True, username__startswith="bitbucket")
|
|
||||||
|
|
||||||
return user
|
|
||||||
|
|
|
@ -14,6 +14,10 @@ from taiga.hooks.exceptions import ActionSyntaxException
|
||||||
from taiga.projects.issues.models import Issue
|
from taiga.projects.issues.models import Issue
|
||||||
from taiga.projects.tasks.models import Task
|
from taiga.projects.tasks.models import Task
|
||||||
from taiga.projects.userstories.models import UserStory
|
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 taiga.projects import services
|
||||||
from .. import factories as f
|
from .. import factories as f
|
||||||
|
|
||||||
|
@ -30,8 +34,9 @@ def test_bad_signature(client):
|
||||||
|
|
||||||
url = reverse("bitbucket-hook-list")
|
url = reverse("bitbucket-hook-list")
|
||||||
url = "{}?project={}&key={}".format(url, project.id, "badbadbad")
|
url = "{}?project={}&key={}".format(url, project.id, "badbadbad")
|
||||||
data = {}
|
data = "{}"
|
||||||
response = client.post(url, urllib.parse.urlencode(data, True), content_type="application/x-www-form-urlencoded")
|
response = client.post(url, data, content_type="application/json", HTTP_X_EVENT_KEY="repo:push")
|
||||||
|
|
||||||
response_content = response.data
|
response_content = response.data
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert "Bad signature" in response_content["_error_message"]
|
assert "Bad signature" in response_content["_error_message"]
|
||||||
|
@ -47,10 +52,11 @@ def test_ok_signature(client):
|
||||||
|
|
||||||
url = reverse("bitbucket-hook-list")
|
url = reverse("bitbucket-hook-list")
|
||||||
url = "{}?project={}&key={}".format(url, project.id, "tpnIwJDz4e")
|
url = "{}?project={}&key={}".format(url, project.id, "tpnIwJDz4e")
|
||||||
data = {'payload': ['{"commits": []}']}
|
data = json.dumps({"push": {"changes": [{"new": {"target": { "message": "test message"}}}]}})
|
||||||
response = client.post(url,
|
response = client.post(url,
|
||||||
urllib.parse.urlencode(data, True),
|
data,
|
||||||
content_type="application/x-www-form-urlencoded",
|
content_type="application/json",
|
||||||
|
HTTP_X_EVENT_KEY="repo:push",
|
||||||
REMOTE_ADDR=settings.BITBUCKET_VALID_ORIGIN_IPS[0])
|
REMOTE_ADDR=settings.BITBUCKET_VALID_ORIGIN_IPS[0])
|
||||||
assert response.status_code == 204
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
@ -65,10 +71,11 @@ def test_invalid_ip(client):
|
||||||
|
|
||||||
url = reverse("bitbucket-hook-list")
|
url = reverse("bitbucket-hook-list")
|
||||||
url = "{}?project={}&key={}".format(url, project.id, "tpnIwJDz4e")
|
url = "{}?project={}&key={}".format(url, project.id, "tpnIwJDz4e")
|
||||||
data = {'payload': ['{"commits": []}']}
|
data = json.dumps({"push": {"changes": [{"new": {"target": { "message": "test message"}}}]}})
|
||||||
response = client.post(url,
|
response = client.post(url,
|
||||||
urllib.parse.urlencode(data, True),
|
data,
|
||||||
content_type="application/x-www-form-urlencoded",
|
content_type="application/json",
|
||||||
|
HTTP_X_EVENT_KEY="repo:push",
|
||||||
REMOTE_ADDR="111.111.111.112")
|
REMOTE_ADDR="111.111.111.112")
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
@ -84,10 +91,11 @@ def test_valid_local_network_ip(client):
|
||||||
|
|
||||||
url = reverse("bitbucket-hook-list")
|
url = reverse("bitbucket-hook-list")
|
||||||
url = "{}?project={}&key={}".format(url, project.id, "tpnIwJDz4e")
|
url = "{}?project={}&key={}".format(url, project.id, "tpnIwJDz4e")
|
||||||
data = {'payload': ['{"commits": []}']}
|
data = json.dumps({"push": {"changes": [{"new": {"target": { "message": "test message"}}}]}})
|
||||||
response = client.post(url,
|
response = client.post(url,
|
||||||
urllib.parse.urlencode(data, True),
|
data,
|
||||||
content_type="application/x-www-form-urlencoded",
|
content_type="application/json",
|
||||||
|
HTTP_X_EVENT_KEY="repo:push",
|
||||||
REMOTE_ADDR="192.168.1.1")
|
REMOTE_ADDR="192.168.1.1")
|
||||||
assert response.status_code == 204
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
@ -103,10 +111,11 @@ def test_not_ip_filter(client):
|
||||||
|
|
||||||
url = reverse("bitbucket-hook-list")
|
url = reverse("bitbucket-hook-list")
|
||||||
url = "{}?project={}&key={}".format(url, project.id, "tpnIwJDz4e")
|
url = "{}?project={}&key={}".format(url, project.id, "tpnIwJDz4e")
|
||||||
data = {'payload': ['{"commits": []}']}
|
data = json.dumps({"push": {"changes": [{"new": {"target": { "message": "test message"}}}]}})
|
||||||
response = client.post(url,
|
response = client.post(url,
|
||||||
urllib.parse.urlencode(data, True),
|
data,
|
||||||
content_type="application/x-www-form-urlencoded",
|
content_type="application/json",
|
||||||
|
HTTP_X_EVENT_KEY="repo:push",
|
||||||
REMOTE_ADDR="111.111.111.112")
|
REMOTE_ADDR="111.111.111.112")
|
||||||
assert response.status_code == 204
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
@ -115,13 +124,14 @@ def test_push_event_detected(client):
|
||||||
project = f.ProjectFactory()
|
project = f.ProjectFactory()
|
||||||
url = reverse("bitbucket-hook-list")
|
url = reverse("bitbucket-hook-list")
|
||||||
url = "%s?project=%s" % (url, project.id)
|
url = "%s?project=%s" % (url, project.id)
|
||||||
data = {'payload': ['{"commits": [{"message": "test message"}]}']}
|
data = json.dumps({"push": {"changes": [{"new": {"target": { "message": "test message"}}}]}})
|
||||||
|
|
||||||
BitBucketViewSet._validate_signature = mock.Mock(return_value=True)
|
BitBucketViewSet._validate_signature = mock.Mock(return_value=True)
|
||||||
|
|
||||||
with mock.patch.object(event_hooks.PushEventHook, "process_event") as process_event_mock:
|
with mock.patch.object(event_hooks.PushEventHook, "process_event") as process_event_mock:
|
||||||
response = client.post(url, urllib.parse.urlencode(data, True),
|
response = client.post(url, data,
|
||||||
content_type="application/x-www-form-urlencoded")
|
HTTP_X_EVENT_KEY="repo:push",
|
||||||
|
content_type="application/json")
|
||||||
|
|
||||||
assert process_event_mock.call_count == 1
|
assert process_event_mock.call_count == 1
|
||||||
|
|
||||||
|
@ -134,9 +144,7 @@ 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 = [
|
payload = {"push": {"changes": [{"new": {"target": { "message": "test message test TG-%s #%s ok bye!" % (issue.ref, new_status.slug)}}}]}}
|
||||||
'{"commits": [{"message": "test message test TG-%s #%s ok bye!"}]}' % (issue.ref, new_status.slug)
|
|
||||||
]
|
|
||||||
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()
|
||||||
|
@ -151,9 +159,7 @@ 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 = [
|
payload = {"push": {"changes": [{"new": {"target": { "message": "test message test TG-%s #%s ok bye!" % (task.ref, new_status.slug)}}}]}}
|
||||||
'{"commits": [{"message": "test message test TG-%s #%s ok bye!"}]}' % (task.ref, new_status.slug)
|
|
||||||
]
|
|
||||||
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()
|
||||||
|
@ -168,9 +174,7 @@ 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 = [
|
payload = {"push": {"changes": [{"new": {"target": { "message": "test message test TG-%s #%s ok bye!" % (user_story.ref, new_status.slug)}}}]}}
|
||||||
'{"commits": [{"message": "test message test TG-%s #%s ok bye!"}]}' % (user_story.ref, new_status.slug)
|
|
||||||
]
|
|
||||||
mail.outbox = []
|
mail.outbox = []
|
||||||
ev_hook = event_hooks.PushEventHook(user_story.project, payload)
|
ev_hook = event_hooks.PushEventHook(user_story.project, payload)
|
||||||
ev_hook.process_event()
|
ev_hook.process_event()
|
||||||
|
@ -186,9 +190,7 @@ 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 = [
|
payload = {"push": {"changes": [{"new": {"target": { "message": "test message test TG-%s #%s ok test TG-%s #%s ok bye!" % (issue1.ref, new_status.slug, issue2.ref, new_status.slug)}}}]}}
|
||||||
'{"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)
|
|
||||||
]
|
|
||||||
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()
|
||||||
|
@ -205,9 +207,7 @@ 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 = [
|
payload = {"push": {"changes": [{"new": {"target": { "message": "test message test TG-%s #%s ok bye!" % (task.ref, new_status.slug)}}}]}}
|
||||||
'{"commits": [{"message": "test message test tg-%s #%s ok bye!"}]}' % (task.ref, new_status.slug.upper())
|
|
||||||
]
|
|
||||||
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()
|
||||||
|
@ -218,9 +218,7 @@ 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 = {"push": {"changes": [{"new": {"target": { "message": "test message test TG-6666666 #%s ok bye!" % (issue_status.slug)}}}]}}
|
||||||
'{"commits": [{"message": "test message test TG-6666666 #%s ok bye!"}]}' % (issue_status.slug)
|
|
||||||
]
|
|
||||||
mail.outbox = []
|
mail.outbox = []
|
||||||
|
|
||||||
ev_hook = event_hooks.PushEventHook(issue_status.project, payload)
|
ev_hook = event_hooks.PushEventHook(issue_status.project, payload)
|
||||||
|
@ -233,9 +231,7 @@ 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 = {"push": {"changes": [{"new": {"target": { "message": "test message test TG-%s #non-existing-slug ok bye!" % (user_story.ref)}}}]}}
|
||||||
'{"commits": [{"message": "test message test TG-%s #non-existing-slug ok bye!"}]}' % (user_story.ref)
|
|
||||||
]
|
|
||||||
|
|
||||||
mail.outbox = []
|
mail.outbox = []
|
||||||
|
|
||||||
|
@ -249,9 +245,7 @@ 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 = {"push": {"changes": [{"new": {"target": { "message": "test message test TG-%s #non-existing-slug ok bye!" % (issue.ref)}}}]}}
|
||||||
'{"commits": [{"message": "test message test TG-%s #non-existing-slug ok bye!"}]}' % (issue.ref)
|
|
||||||
]
|
|
||||||
mail.outbox = []
|
mail.outbox = []
|
||||||
|
|
||||||
ev_hook = event_hooks.PushEventHook(issue.project, payload)
|
ev_hook = event_hooks.PushEventHook(issue.project, payload)
|
||||||
|
@ -262,6 +256,217 @@ def test_push_event_bad_processing_non_existing_status(client):
|
||||||
assert len(mail.outbox) == 0
|
assert len(mail.outbox) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_issues_event_opened_issue(client):
|
||||||
|
issue = f.IssueFactory.create()
|
||||||
|
issue.project.default_issue_status = issue.status
|
||||||
|
issue.project.default_issue_type = issue.type
|
||||||
|
issue.project.default_severity = issue.severity
|
||||||
|
issue.project.default_priority = issue.priority
|
||||||
|
issue.project.save()
|
||||||
|
Membership.objects.create(user=issue.owner, project=issue.project, role=f.RoleFactory.create(project=issue.project), is_owner=True)
|
||||||
|
notify_policy = NotifyPolicy.objects.get(user=issue.owner, project=issue.project)
|
||||||
|
notify_policy.notify_level = NotifyLevel.watch
|
||||||
|
notify_policy.save()
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"actor": {
|
||||||
|
"user": {
|
||||||
|
"uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
|
||||||
|
"username": "test-user",
|
||||||
|
"links": {"html": {"href": "http://bitbucket.com/test-user"}}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"issue": {
|
||||||
|
"id": "10",
|
||||||
|
"title": "test-title",
|
||||||
|
"links": {"html": {"href": "http://bitbucket.com/site/master/issue/10"}},
|
||||||
|
"content": {"raw": "test-content"}
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"links": {"html": {"href": "http://bitbucket.com/test-user/test-project"}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mail.outbox = []
|
||||||
|
|
||||||
|
ev_hook = event_hooks.IssuesEventHook(issue.project, payload)
|
||||||
|
ev_hook.process_event()
|
||||||
|
|
||||||
|
assert Issue.objects.count() == 2
|
||||||
|
assert len(mail.outbox) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_issues_event_bad_issue(client):
|
||||||
|
issue = f.IssueFactory.create()
|
||||||
|
issue.project.default_issue_status = issue.status
|
||||||
|
issue.project.default_issue_type = issue.type
|
||||||
|
issue.project.default_severity = issue.severity
|
||||||
|
issue.project.default_priority = issue.priority
|
||||||
|
issue.project.save()
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"actor": {
|
||||||
|
},
|
||||||
|
"issue": {
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mail.outbox = []
|
||||||
|
|
||||||
|
ev_hook = event_hooks.IssuesEventHook(issue.project, payload)
|
||||||
|
|
||||||
|
with pytest.raises(ActionSyntaxException) as excinfo:
|
||||||
|
ev_hook.process_event()
|
||||||
|
|
||||||
|
assert str(excinfo.value) == "Invalid issue information"
|
||||||
|
|
||||||
|
assert Issue.objects.count() == 1
|
||||||
|
assert len(mail.outbox) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_comment_event_on_existing_issue_task_and_us(client):
|
||||||
|
project = f.ProjectFactory()
|
||||||
|
role = f.RoleFactory(project=project, permissions=["view_tasks", "view_issues", "view_us"])
|
||||||
|
f.MembershipFactory(project=project, role=role, user=project.owner)
|
||||||
|
user = f.UserFactory()
|
||||||
|
|
||||||
|
issue = f.IssueFactory.create(external_reference=["bitbucket", "http://bitbucket.com/site/master/issue/11"], owner=project.owner, project=project)
|
||||||
|
take_snapshot(issue, user=user)
|
||||||
|
task = f.TaskFactory.create(external_reference=["bitbucket", "http://bitbucket.com/site/master/issue/11"], owner=project.owner, project=project)
|
||||||
|
take_snapshot(task, user=user)
|
||||||
|
us = f.UserStoryFactory.create(external_reference=["bitbucket", "http://bitbucket.com/site/master/issue/11"], owner=project.owner, project=project)
|
||||||
|
take_snapshot(us, user=user)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"actor": {
|
||||||
|
"user": {
|
||||||
|
"uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
|
||||||
|
"username": "test-user",
|
||||||
|
"links": {"html": {"href": "http://bitbucket.com/test-user"}}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"issue": {
|
||||||
|
"id": "11",
|
||||||
|
"title": "test-title",
|
||||||
|
"links": {"html": {"href": "http://bitbucket.com/site/master/issue/11"}},
|
||||||
|
"content": {"raw": "test-content"}
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"content": {"raw": "Test body"},
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"links": {"html": {"href": "http://bitbucket.com/test-user/test-project"}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mail.outbox = []
|
||||||
|
|
||||||
|
assert get_history_queryset_by_model_instance(issue).count() == 0
|
||||||
|
assert get_history_queryset_by_model_instance(task).count() == 0
|
||||||
|
assert get_history_queryset_by_model_instance(us).count() == 0
|
||||||
|
|
||||||
|
ev_hook = event_hooks.IssueCommentEventHook(issue.project, payload)
|
||||||
|
ev_hook.process_event()
|
||||||
|
|
||||||
|
issue_history = get_history_queryset_by_model_instance(issue)
|
||||||
|
assert issue_history.count() == 1
|
||||||
|
assert "Test body" in issue_history[0].comment
|
||||||
|
|
||||||
|
task_history = get_history_queryset_by_model_instance(task)
|
||||||
|
assert task_history.count() == 1
|
||||||
|
assert "Test body" in issue_history[0].comment
|
||||||
|
|
||||||
|
us_history = get_history_queryset_by_model_instance(us)
|
||||||
|
assert us_history.count() == 1
|
||||||
|
assert "Test body" in issue_history[0].comment
|
||||||
|
|
||||||
|
assert len(mail.outbox) == 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_comment_event_on_not_existing_issue_task_and_us(client):
|
||||||
|
issue = f.IssueFactory.create(external_reference=["bitbucket", "10"])
|
||||||
|
take_snapshot(issue, user=issue.owner)
|
||||||
|
task = f.TaskFactory.create(project=issue.project, external_reference=["bitbucket", "10"])
|
||||||
|
take_snapshot(task, user=task.owner)
|
||||||
|
us = f.UserStoryFactory.create(project=issue.project, external_reference=["bitbucket", "10"])
|
||||||
|
take_snapshot(us, user=us.owner)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"actor": {
|
||||||
|
"user": {
|
||||||
|
"uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
|
||||||
|
"username": "test-user",
|
||||||
|
"links": {"html": {"href": "http://bitbucket.com/test-user"}}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"issue": {
|
||||||
|
"id": "10",
|
||||||
|
"title": "test-title",
|
||||||
|
"links": {"html": {"href": "http://bitbucket.com/site/master/issue/10"}},
|
||||||
|
"content": {"raw": "test-content"}
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"content": {"raw": "Test body"},
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"links": {"html": {"href": "http://bitbucket.com/test-user/test-project"}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mail.outbox = []
|
||||||
|
|
||||||
|
assert get_history_queryset_by_model_instance(issue).count() == 0
|
||||||
|
assert get_history_queryset_by_model_instance(task).count() == 0
|
||||||
|
assert get_history_queryset_by_model_instance(us).count() == 0
|
||||||
|
|
||||||
|
ev_hook = event_hooks.IssueCommentEventHook(issue.project, payload)
|
||||||
|
ev_hook.process_event()
|
||||||
|
|
||||||
|
assert get_history_queryset_by_model_instance(issue).count() == 0
|
||||||
|
assert get_history_queryset_by_model_instance(task).count() == 0
|
||||||
|
assert get_history_queryset_by_model_instance(us).count() == 0
|
||||||
|
|
||||||
|
assert len(mail.outbox) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_issues_event_bad_comment(client):
|
||||||
|
issue = f.IssueFactory.create(external_reference=["bitbucket", "10"])
|
||||||
|
take_snapshot(issue, user=issue.owner)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"actor": {
|
||||||
|
"user": {
|
||||||
|
"uuid": "{ce1054cd-3f43-49dc-8aea-d3085ee7ec9b}",
|
||||||
|
"username": "test-user",
|
||||||
|
"links": {"html": {"href": "http://bitbucket.com/test-user"}}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"issue": {
|
||||||
|
"id": "10",
|
||||||
|
"title": "test-title",
|
||||||
|
"links": {"html": {"href": "http://bitbucket.com/site/master/issue/10"}},
|
||||||
|
"content": {"raw": "test-content"}
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"links": {"html": {"href": "http://bitbucket.com/test-user/test-project"}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ev_hook = event_hooks.IssueCommentEventHook(issue.project, payload)
|
||||||
|
|
||||||
|
mail.outbox = []
|
||||||
|
|
||||||
|
with pytest.raises(ActionSyntaxException) as excinfo:
|
||||||
|
ev_hook.process_event()
|
||||||
|
|
||||||
|
assert str(excinfo.value) == "Invalid issue comment information"
|
||||||
|
|
||||||
|
assert Issue.objects.count() == 1
|
||||||
|
assert len(mail.outbox) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_api_get_project_modules(client):
|
def test_api_get_project_modules(client):
|
||||||
project = f.create_project()
|
project = f.create_project()
|
||||||
f.MembershipFactory(project=project, user=project.owner, is_owner=True)
|
f.MembershipFactory(project=project, user=project.owner, is_owner=True)
|
||||||
|
|
Loading…
Reference in New Issue