Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(AccessLogsExport): export in background TASK-1145 #5216

Merged
merged 9 commits into from
Nov 5, 2024
5 changes: 3 additions & 2 deletions kobo/apps/project_views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
from kpi.permissions import IsAuthenticated
from kpi.serializers.v2.asset import AssetMetadataListSerializer
from kpi.serializers.v2.user import UserListSerializer
from kpi.tasks import export_task_in_background
from kpi.utils.object_permission import get_database_user
from kpi.utils.project_views import (
get_region_for_view,
user_has_view_perms,
)
from kpi.tasks import project_view_export_in_background
from .models.project_view import ProjectView
from .serializers import ProjectViewSerializer

Expand Down Expand Up @@ -110,9 +110,10 @@ def export(self, request, uid, obj_type):
)

# Have Celery run the export in the background
project_view_export_in_background.delay(
export_task_in_background.delay(
export_task_uid=export_task.uid,
username=user.username,
export_task_class=ProjectViewExportTask,
)

return Response({'status': export_task.status})
Expand Down
13 changes: 7 additions & 6 deletions kpi/tasks.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# coding: utf-8
import time
from typing import Type

import constance
import requests
from django.conf import settings
from django.core.mail import send_mail
from django.core import mail
from django.core.management import call_command

from kobo.apps.kobo_auth.shortcuts import User
Expand All @@ -13,7 +14,7 @@
from kpi.constants import LIMIT_HOURS_23
from kpi.maintenance_tasks import remove_old_asset_snapshots, remove_old_import_tasks
from kpi.models.asset import Asset
from kpi.models.import_export_task import ExportTask, ImportTask, ProjectViewExportTask
from kpi.models.import_export_task import ExportTask, ImportExportTask, ImportTask


@celery_app.task
Expand All @@ -29,12 +30,12 @@ def export_in_background(export_task_uid):


@celery_app.task
def project_view_export_in_background(
export_task_uid: str, username: str
def export_task_in_background(
export_task_uid: str, username: str, export_task_class: Type[ImportExportTask]
) -> None:
user = User.objects.get(username=username)

export_task = ProjectViewExportTask.objects.get(uid=export_task_uid)
export_task = export_task_class.objects.get(uid=export_task_uid)
export = export_task.run()
if export.status == 'complete' and export.result:
file_url = f'{settings.KOBOFORM_URL}{export.result.url}'
Expand All @@ -44,7 +45,7 @@ def project_view_export_in_background(
'Regards,\n'
'KoboToolbox'
)
send_mail(
mail.send_mail(
subject='Project View Report Complete',
message=msg,
from_email=constance.config.SUPPORT_EMAIL,
Expand Down
110 changes: 110 additions & 0 deletions kpi/tests/test_export_tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import datetime
from unittest.mock import Mock, patch

from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.test import TestCase

from kobo.apps.kobo_auth.shortcuts import User
from kpi.models.import_export_task import ProjectViewExportTask
from kpi.tasks import export_task_in_background


class ExportTaskInBackgroundTests(TestCase):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can patch the whole class at once instead of each individual method

Suggested change
class ExportTaskInBackgroundTests(TestCase):
@patch('django.core.mail.send_mail')
@patch('kobo.apps.project_views.models.project_view.ProjectView.objects.get')
class ExportTaskInBackgroundTests(TestCase):

def setUp(self):
self.user = User.objects.create(username='someuser', email='[email protected]')
self.mock_data = {'type': 'assets', 'view': 'summary'}
self.task = ProjectViewExportTask.objects.create(
uid='test_uid', data=self.mock_data, user=self.user
)
self.project_view = Mock()
self.project_view.get_countries.return_value = []

@patch('django.core.mail.send_mail')
@patch('kobo.apps.project_views.models.project_view.ProjectView.objects.get')
def test_export_task_success(self, mock_get_project_view, mock_send_mail):
mock_get_project_view.return_value = self.project_view
self.task.run = Mock(return_value=self.task)

export_task_in_background(
self.task.uid, self.user.username, ProjectViewExportTask
)

self.task.refresh_from_db()
self.assertEqual(self.task.status, 'complete')

mock_send_mail.assert_called_once()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shortcut: you can use assert_called_once_with and pass the expected arguments

args, kwargs = mock_send_mail.call_args
root_url = settings.KOBOFORM_URL
expected_message = (
'Hello {},\n\n'
'Your report is complete: {}'
'/private-media/{}/exports/'
'assets-{}-view_summary-{}.csv\n\n'
'Regards,\n'
'KoboToolbox'
).format(
self.user.username,
root_url,
self.user.username,
self.user.username,
datetime.datetime.now().strftime('%Y-%m-%dT%H%M%SZ'),
)
self.assertEqual(kwargs['subject'], 'Project View Report Complete')
self.assertEqual(expected_message, kwargs['message'])

@patch('django.core.mail.send_mail')
@patch('kobo.apps.project_views.models.project_view.ProjectView.objects.get')
def test_invalid_export_task_uid(self, mock_get_project_view, mock_send_mail):
mock_get_project_view.side_effect = ObjectDoesNotExist
with self.assertRaisesMessage(
ObjectDoesNotExist, 'ProjectViewExportTask matching query does not exist.'
):
export_task_in_background(
'invalid_uid', self.user.username, ProjectViewExportTask
)

mock_send_mail.assert_not_called()

@patch('django.core.mail.send_mail')
@patch('kobo.apps.project_views.models.project_view.ProjectView.objects.get')
def test_invalid_username(self, mock_get_project_view, mock_send_mail):
with self.assertRaisesMessage(
ObjectDoesNotExist, 'User matching query does not exist.'
):
export_task_in_background(
self.task.uid, 'invalid_username', ProjectViewExportTask
)

mock_send_mail.assert_not_called()

@patch('django.core.mail.send_mail')
@patch('kobo.apps.project_views.models.project_view.ProjectView.objects.get')
@patch('kpi.models.ProjectViewExportTask._run_task')
def test_export_task_error(
self, mock_run_task, mock_get_project_view, mock_send_mail
):
mock_get_project_view.return_value = self.project_view
mock_run_task.side_effect = Exception('Simulated task failure')

export_task_in_background(
self.task.uid, self.user.username, ProjectViewExportTask
)

self.task.refresh_from_db()
self.assertEqual(self.task.status, 'error')

@patch('django.core.mail.send_mail')
@patch('kobo.apps.project_views.models.project_view.ProjectView.objects.get')
@patch('kpi.models.ProjectViewExportTask._run_task')
def test_email_not_sent_if_export_errors(
self, mock_run_task, mock_get_project_view, mock_send_mail
):
mock_get_project_view.return_value = self.project_view
mock_run_task.side_effect = Exception('Simulated task failure')

export_task_in_background(
self.task.uid, self.user.username, ProjectViewExportTask
)

mock_send_mail.assert_not_called()
Loading