Skip to content

Commit

Permalink
Add SimpleIssueProvider and fix tests for integrations
Browse files Browse the repository at this point in the history
  • Loading branch information
jochenklar committed Nov 17, 2023
1 parent 599913e commit ff08f12
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 50 deletions.
64 changes: 62 additions & 2 deletions rdmo/projects/providers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import json

from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponseRedirect
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _

Expand All @@ -12,7 +14,7 @@ class IssueProvider(Plugin):
def send_issue(self, request, issue, integration, subject, message, attachments):
raise NotImplementedError

def webhook(self, request, options, payload):
def webhook(self, request, integration):
raise NotImplementedError


Expand Down Expand Up @@ -69,3 +71,61 @@ def get_post_data(self, request, issue, integration, subject, message, attachmen

def get_issue_url(self, response):
raise NotImplementedError


class SimpleIssueProvider(OauthIssueProvider):

add_label = _('Add Simple integration')
send_label = _('Send to Simple')
description = _('This integration allow the creation of issues in arbitrary Simple repositories. '
'The upload of attachments is not supported.')

@property
def fields(self):
return [
{
'key': 'project_url',
'placeholder': 'https://example.com/projects/<name>',
'help': _('The URL of the project to send tasks to.')
},
{
'key': 'secret',
'placeholder': 'Secret (random) string',
'help': _('The secret for a webhook to close a task (optional).'),
'required': False,
'secret': True
}
]

def get(self, request, url):
raise NotImplementedError

def post(self, request, url, json=None, files=None, multipart=None):
raise NotImplementedError

def webhook(self, request, integration):
secret = integration.get_option_value('secret')
header_signature = request.headers.get('X-Secret')
if secret == header_signature:
try:
payload = json.loads(request.body.decode())
except json.decoder.JSONDecodeError as e:
return HttpResponse(e, status=400)

action = payload.get('action')
url = payload.get('url')

try:
issue_resource = integration.resources.get(url=url)
if action == 'closed':
issue_resource.issue.status = issue_resource.issue.ISSUE_STATUS_CLOSED
else:
issue_resource.issue.status = issue_resource.issue.ISSUE_STATUS_IN_PROGRESS

issue_resource.issue.save()
except ObjectDoesNotExist:
pass

return HttpResponse(status=200)

raise Http404
49 changes: 32 additions & 17 deletions rdmo/projects/tests/test_view_integration.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import hmac
import json

import pytest

from django.urls import reverse
Expand Down Expand Up @@ -42,7 +39,7 @@
def test_integration_create_get(db, client, username, password, project_id):
client.login(username=username, password=password)

url = reverse('integration_create', args=[project_id, 'github'])
url = reverse('integration_create', args=[project_id, 'simple'])
response = client.get(url)

if project_id in add_integration_permission_map.get(username, []):
Expand All @@ -58,9 +55,9 @@ def test_integration_create_get(db, client, username, password, project_id):
def test_integration_create_post(db, client, username, password, project_id):
client.login(username=username, password=password)

url = reverse('integration_create', args=[project_id, 'github'])
url = reverse('integration_create', args=[project_id, 'simple'])
data = {
'repo': 'example/example1'
'project_url': 'https://example.com/projects/1'
}
response = client.post(url, data)

Expand All @@ -69,8 +66,8 @@ def test_integration_create_post(db, client, username, password, project_id):
values = Integration.objects.order_by('id').last().options.values('key', 'value', 'secret')
assert sorted(values, key=lambda obj: obj['key']) == [
{
'key': 'repo',
'value': 'example/example1',
'key': 'project_url',
'value': 'https://example.com/projects/1',
'secret': False
},
{
Expand Down Expand Up @@ -115,7 +112,7 @@ def test_integration_update_post(db, client, username, password, project_id, int

url = reverse('integration_update', args=[project_id, integration_id])
data = {
'repo': 'example/example2',
'project_url': 'https://example.com/projects/2',
'secret': 'super_secret'
}
response = client.post(url, data)
Expand All @@ -127,8 +124,8 @@ def test_integration_update_post(db, client, username, password, project_id, int
.options.values('key', 'value', 'secret')
assert sorted(values, key=lambda obj: obj['key']) == [
{
'key': 'repo',
'value': 'example/example2',
'key': 'project_url',
'value': 'https://example.com/projects/2',
'secret': False
},
{
Expand Down Expand Up @@ -207,14 +204,10 @@ def test_integration_webhook_post(db, client, project_id, integration_id):
url = reverse('integration_webhook', args=[project_id, integration_id])
data = {
'action': 'closed',
'issue': {
'html_url': 'https://github.com/example/example/issues/1'
}
'url': 'https://simple.example.com/issues/1'
}
body = json.dumps(data)
signature = 'sha1=' + hmac.new(secret.encode(), body.encode(), 'sha1').hexdigest()

response = client.post(url, data, **{'HTTP_X_HUB_SIGNATURE': signature, 'content_type': 'application/json'})
response = client.post(url, data, **{'HTTP_X_SECRET': secret, 'content_type': 'application/json'})

if integration:
assert response.status_code == 200
Expand All @@ -224,6 +217,28 @@ def test_integration_webhook_post(db, client, project_id, integration_id):
assert Issue.objects.filter(status='closed').count() == 0


@pytest.mark.parametrize('project_id', projects)
@pytest.mark.parametrize('integration_id', integrations)
def test_integration_webhook_post_wrong_url(db, client, project_id, integration_id):
integration = Integration.objects.filter(project_id=project_id, id=integration_id).first()

secret = 'super_duper_secret'
url = reverse('integration_webhook', args=[project_id, integration_id])
data = {
'action': 'closed',
'url': 'https://simple.example.com/issues/2'
}

response = client.post(url, data, **{'HTTP_X_SECRET': secret, 'content_type': 'application/json'})

if integration:
assert response.status_code == 200
assert Issue.objects.filter(status='closed').count() == 0
else:
assert response.status_code == 404
assert Issue.objects.filter(status='closed').count() == 0


@pytest.mark.parametrize('project_id', projects)
@pytest.mark.parametrize('integration_id', integrations)
def test_integration_webhook_post_no_secret(db, client, project_id, integration_id):
Expand Down
12 changes: 9 additions & 3 deletions rdmo/projects/tests/test_view_issue.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from unittest.mock import Mock

import pytest

from django.core import mail
from django.http import HttpResponseRedirect
from django.urls import reverse

from rdmo.core.constants import VALUE_TYPE_FILE
Expand Down Expand Up @@ -224,7 +227,10 @@ def test_issue_send_post_attachements(db, client, files, username, password, pro
@pytest.mark.parametrize('username,password', users)
@pytest.mark.parametrize('issue_id', issues)
@pytest.mark.parametrize('project_id', projects)
def test_issue_send_post_integration(db, client, username, password, project_id, issue_id):
def test_issue_send_post_integration(db, client, mocker, username, password, project_id, issue_id):
mocked_send_issue = Mock(return_value=HttpResponseRedirect(redirect_to='https://example.com/login/oauth/authorize'))
mocker.patch('rdmo.projects.providers.SimpleIssueProvider.send_issue', mocked_send_issue)

client.login(username=username, password=password)
issue = Issue.objects.filter(project_id=project_id, id=issue_id).first()

Expand All @@ -240,14 +246,14 @@ def test_issue_send_post_integration(db, client, username, password, project_id,
if project_id in change_issue_permission_map.get(username, []):
if integration_pk in Project.objects.get(pk=project_id).integrations.values_list('id', flat=True):
assert response.status_code == 302
assert response.url.startswith('https://github.com')
assert response.url.startswith('https://example.com')
else:
assert response.status_code == 200
else:
if password:
assert response.status_code == 403
else:
assert response.status_code == 302
assert not response.url.startswith('https://github.com')
assert not response.url.startswith('https://example.com')
else:
assert response.status_code == 404
30 changes: 15 additions & 15 deletions rdmo/projects/tests/test_viewset_project_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,11 @@ def test_create(db, client, username, password, project_id):

url = reverse(urlnames['list'], args=[project_id])
data = {
'provider_key': 'github',
'provider_key': 'simple',
'options': [
{
'key': 'repo',
'value': 'example/example'
'key': 'project_url',
'value': 'https://example.com/projects/1'
}
]
}
Expand All @@ -115,8 +115,8 @@ def test_create_error1(db, client, username, password, project_id):
'provider_key': 'wrong',
'options': [
{
'key': 'repo',
'value': 'example/example'
'key': 'project_url',
'value': 'https://example.com/projects/1'
}
]
}
Expand All @@ -138,10 +138,10 @@ def test_create_error2(db, client, username, password, project_id):

url = reverse(urlnames['list'], args=[project_id])
data = {
'provider_key': 'github',
'provider_key': 'simple',
'options': [
{
'key': 'repo',
'key': 'project_url',
'value': ''
}
]
Expand All @@ -164,11 +164,11 @@ def test_create_error3(db, client, username, password, project_id):

url = reverse(urlnames['list'], args=[project_id])
data = {
'provider_key': 'github',
'provider_key': 'simple',
'options': [
{
'key': 'repo',
'value': 'example/example'
'key': 'project_url',
'value': 'https://example.com/projects/1'
},
{
'key': 'foo',
Expand Down Expand Up @@ -196,11 +196,11 @@ def test_update(db, client, username, password, project_id, integration_id):

url = reverse(urlnames['detail'], args=[project_id, integration_id])
data = {
'provider_key': 'github',
'provider_key': 'simple',
'options': [
{
'key': 'repo',
'value': 'example/test'
'key': 'project_url',
'value': 'https://example.com/projects/2'
}
]
}
Expand All @@ -210,8 +210,8 @@ def test_update(db, client, username, password, project_id, integration_id):
assert response.status_code == 200
assert sorted(response.json().get('options'), key=lambda obj: obj['key']) == [
{
'key': 'repo',
'value': 'example/test'
'key': 'project_url',
'value': 'https://example.com/projects/2'
},
{
'key': 'secret',
Expand Down
7 changes: 1 addition & 6 deletions testing/config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,5 @@
]

PROJECT_ISSUE_PROVIDERS = [
('github', _('GitHub'), 'rdmo.projects.providers.GitHubIssueProvider')
('simple', _('Simple provider'), 'rdmo.projects.providers.SimpleIssueProvider')
]

GITHUB_PROVIDER = {
'client_id': '',
'client_secret': ''
}
14 changes: 7 additions & 7 deletions testing/fixtures/projects.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,24 @@
"pk": 1,
"fields": {
"project": 1,
"provider_key": "github"
"provider_key": "simple"
}
},
{
"model": "projects.integration",
"pk": 2,
"fields": {
"project": 2,
"provider_key": "github"
"provider_key": "simple"
}
},
{
"model": "projects.integrationoption",
"pk": 1,
"fields": {
"integration": 1,
"key": "repo",
"value": "example/example",
"key": "project_url",
"value": "https://example.com/projects/1",
"secret": false
}
},
Expand All @@ -51,8 +51,8 @@
"pk": 3,
"fields": {
"integration": 2,
"key": "repo",
"value": "example/example",
"key": "project_url",
"value": "https://example.com/projects/1",
"secret": false
}
},
Expand Down Expand Up @@ -135,7 +135,7 @@
"fields": {
"issue": 1,
"integration": 1,
"url": "https://github.com/example/example/issues/1"
"url": "https://simple.example.com/issues/1"
}
},
{
Expand Down

0 comments on commit ff08f12

Please sign in to comment.