Skip to content

Commit

Permalink
Merge branch 'main' into task-979-Add-org-name-badge-to-app-header-na…
Browse files Browse the repository at this point in the history
…v-updates
  • Loading branch information
duvld authored Nov 11, 2024
2 parents ef8af3c + 370933a commit af90e27
Show file tree
Hide file tree
Showing 45 changed files with 1,822 additions and 729 deletions.
5 changes: 3 additions & 2 deletions hub/models/extra_user_detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,6 @@ def _sync_org_name(self):
except (KeyError, AttributeError):
organization_name = None

user_organization.name = organization_name
user_organization.save(update_fields=['name'])
if organization_name:
user_organization.name = organization_name
user_organization.save(update_fields=['name'])
125 changes: 4 additions & 121 deletions kobo/apps/hook/tests/hook_test_case.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
# coding: utf-8
import json
import uuid

import pytest
import responses
from django.conf import settings
from django.urls import reverse
from rest_framework import status

from kpi.constants import SUBMISSION_FORMAT_TYPE_JSON, SUBMISSION_FORMAT_TYPE_XML
from kpi.exceptions import BadFormatException
from kpi.tests.kpi_test_case import KpiTestCase
from ..constants import HOOK_LOG_FAILED
from ..exceptions import HookRemoteServerDownError
from ..models import Hook, HookLog
from ..models import Hook
from ..utils.tests.mixins import HookTestCaseMixin


class HookTestCase(KpiTestCase):
class HookTestCase(HookTestCaseMixin, KpiTestCase):

def setUp(self):
self.client.login(username='someuser', password='someuser')
Expand Down Expand Up @@ -49,115 +39,8 @@ def setUp(self):
self.asset.deploy(backend='mock', active=True)
self.asset.save()
self.hook = Hook()
self._submission_pk = 1

settings.CELERY_TASK_ALWAYS_EAGER = True

def _create_hook(self, return_response_only=False, **kwargs):

format_type = kwargs.get('format_type', SUBMISSION_FORMAT_TYPE_JSON)
if format_type not in [
SUBMISSION_FORMAT_TYPE_JSON,
SUBMISSION_FORMAT_TYPE_XML,
]:
raise BadFormatException(
'The format {} is not supported'.format(format_type)
)

self.__prepare_submission()

url = reverse('hook-list', args=(self.asset.uid,))
data = {
'name': kwargs.get('name', 'some external service with token'),
'endpoint': kwargs.get('endpoint', 'http://external.service.local/'),
'settings': kwargs.get('settings', {
'custom_headers': {
'X-Token': '1234abcd'
}
}),
'export_type': format_type,
'active': kwargs.get('active', True),
'subset_fields': kwargs.get('subset_fields', []),
'payload_template': kwargs.get('payload_template', None)
}

response = self.client.post(url, data, format='json')
if return_response_only:
return response
else:
self.assertEqual(
response.status_code, status.HTTP_201_CREATED, msg=response.data
)
hook = self.asset.hooks.last()
self.assertTrue(hook.active)
return hook

def _send_and_fail(self):
"""
The public method which calls this method needs to be decorated by
`@responses.activate`
:return: dict
"""
first_hooklog_response = self._send_and_wait_for_retry()

# Fakes Celery n retries by forcing status to `failed`
# (where n is `settings.HOOKLOG_MAX_RETRIES`)
first_hooklog = HookLog.objects.get(uid=first_hooklog_response.get('uid'))
first_hooklog.change_status(HOOK_LOG_FAILED)

return first_hooklog_response

def _send_and_wait_for_retry(self):
self.hook = self._create_hook()

ServiceDefinition = self.hook.get_service_definition()
submissions = self.asset.deployment.get_submissions(self.asset.owner)
submission_id = submissions[0]['_id']
service_definition = ServiceDefinition(self.hook, submission_id)
first_mock_response = {'error': 'gateway timeout'}

# Mock first request's try
responses.add(
responses.POST,
self.hook.endpoint,
json=first_mock_response,
status=status.HTTP_504_GATEWAY_TIMEOUT,
)

# Mock next requests' tries
responses.add(
responses.POST,
self.hook.endpoint,
status=status.HTTP_200_OK,
content_type='application/json',
)

# Try to send data to external endpoint
with pytest.raises(HookRemoteServerDownError):
service_definition.send()

# Retrieve the corresponding log
url = reverse('hook-log-list', kwargs={
'parent_lookup_asset': self.hook.asset.uid,
'parent_lookup_hook': self.hook.uid
})

response = self.client.get(url)
first_hooklog_response = response.data.get('results')[0]

# Result should match first try
self.assertEqual(
first_hooklog_response.get('status_code'),
status.HTTP_504_GATEWAY_TIMEOUT,
)
self.assertEqual(
json.loads(first_hooklog_response.get('message')),
first_mock_response,
)
return first_hooklog_response

def __prepare_submission(self):
def _add_submissions(self):
v_uid = self.asset.latest_deployed_version.uid
self.submission = {
'__version__': v_uid,
Expand Down
Empty file.
118 changes: 118 additions & 0 deletions kobo/apps/hook/utils/tests/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import json

import pytest
import responses
from django.urls import reverse
from rest_framework import status

from kpi.constants import SUBMISSION_FORMAT_TYPE_JSON, SUBMISSION_FORMAT_TYPE_XML
from kpi.exceptions import BadFormatException
from kobo.apps.hook.constants import HOOK_LOG_FAILED
from kobo.apps.hook.exceptions import HookRemoteServerDownError
from kobo.apps.hook.models import HookLog


class HookTestCaseMixin:

def _create_hook(self, return_response_only=False, **kwargs):

format_type = kwargs.get('format_type', SUBMISSION_FORMAT_TYPE_JSON)
if format_type not in [
SUBMISSION_FORMAT_TYPE_JSON,
SUBMISSION_FORMAT_TYPE_XML,
]:
raise BadFormatException(
'The format {} is not supported'.format(format_type)
)

self._add_submissions()

url = reverse('hook-list', args=(self.asset.uid,))
data = {
'name': kwargs.get('name', 'some external service with token'),
'endpoint': kwargs.get('endpoint', 'http://external.service.local/'),
'settings': kwargs.get('settings', {
'custom_headers': {
'X-Token': '1234abcd'
}
}),
'export_type': format_type,
'active': kwargs.get('active', True),
'subset_fields': kwargs.get('subset_fields', []),
'payload_template': kwargs.get('payload_template', None)
}

response = self.client.post(url, data, format='json')
if return_response_only:
return response
else:
self.assertEqual(
response.status_code, status.HTTP_201_CREATED, msg=response.data
)
hook = self.asset.hooks.last()
self.assertTrue(hook.active)
return hook

def _send_and_fail(self) -> dict:
"""
The public method which calls this method needs to be decorated by
`@responses.activate`
"""

first_hooklog_response = self._send_and_wait_for_retry()

# Fakes Celery n retries by forcing status to `failed`
# (where n is `settings.HOOKLOG_MAX_RETRIES`)
first_hooklog = HookLog.objects.get(uid=first_hooklog_response.get('uid'))
first_hooklog.change_status(HOOK_LOG_FAILED)

return first_hooklog_response

def _send_and_wait_for_retry(self):
self.hook = self._create_hook()

ServiceDefinition = self.hook.get_service_definition()
submissions = self.asset.deployment.get_submissions(self.asset.owner)
submission_id = submissions[0]['_id']
service_definition = ServiceDefinition(self.hook, submission_id)
first_mock_response = {'error': 'gateway timeout'}

# Mock first request's try
responses.add(
responses.POST,
self.hook.endpoint,
json=first_mock_response,
status=status.HTTP_504_GATEWAY_TIMEOUT,
)

# Mock next requests' tries
responses.add(
responses.POST,
self.hook.endpoint,
status=status.HTTP_200_OK,
content_type='application/json',
)

# Try to send data to external endpoint
with pytest.raises(HookRemoteServerDownError):
service_definition.send()

# Retrieve the corresponding log
url = reverse('hook-log-list', kwargs={
'parent_lookup_asset': self.hook.asset.uid,
'parent_lookup_hook': self.hook.uid
})

response = self.client.get(url)
first_hooklog_response = response.data.get('results')[0]

# Result should match first try
self.assertEqual(
first_hooklog_response.get('status_code'),
status.HTTP_504_GATEWAY_TIMEOUT,
)
self.assertEqual(
json.loads(first_hooklog_response.get('message')),
first_mock_response,
)
return first_hooklog_response
4 changes: 4 additions & 0 deletions kobo/apps/kobo_auth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from kobo.apps.openrosa.libs.permissions import get_model_permission_codenames
from kobo.apps.organizations.models import create_organization, Organization
from kpi.utils.database import update_autofield_sequence, use_db
from kpi.utils.permissions import is_user_anonymous


class User(AbstractUser):
Expand Down Expand Up @@ -52,6 +53,9 @@ def is_org_owner(self):
@property
@cache_for_request
def organization(self):
if is_user_anonymous(self):
return

# Database allows multiple organizations per user, but we restrict it to one.
if organization := Organization.objects.filter(
organization_users__user=self
Expand Down
8 changes: 5 additions & 3 deletions kobo/apps/openrosa/apps/api/viewsets/xform_submission_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,11 @@ def create(self, request, *args, **kwargs):
username = user.username

if request.method.upper() == 'HEAD':
return Response(status=status.HTTP_204_NO_CONTENT,
headers=self.get_openrosa_headers(request),
template_name=self.template_name)
return Response(
status=status.HTTP_204_NO_CONTENT,
headers=self.get_openrosa_headers(request),
template_name=self.template_name,
)

is_json_request = is_json(request)

Expand Down
Loading

0 comments on commit af90e27

Please sign in to comment.