From d9ef70900a220ded71d4386f05e7fcbda4ea7c73 Mon Sep 17 00:00:00 2001 From: rgraber Date: Tue, 4 Nov 2025 11:54:52 -0500 Subject: [PATCH 1/4] test(subsequences): port old unit tests DEV-1231 --- .../v2/{test_validation.py => test_api.py} | 199 ++++++++++++++++++ .../tests/api/v2/test_permissions.py | 4 +- 2 files changed, 201 insertions(+), 2 deletions(-) rename kobo/apps/subsequences/tests/api/v2/{test_validation.py => test_api.py} (55%) diff --git a/kobo/apps/subsequences/tests/api/v2/test_validation.py b/kobo/apps/subsequences/tests/api/v2/test_api.py similarity index 55% rename from kobo/apps/subsequences/tests/api/v2/test_validation.py rename to kobo/apps/subsequences/tests/api/v2/test_api.py index f6a25eaa0a..6da65688e0 100644 --- a/kobo/apps/subsequences/tests/api/v2/test_validation.py +++ b/kobo/apps/subsequences/tests/api/v2/test_api.py @@ -1,13 +1,167 @@ +import uuid from unittest.mock import MagicMock, patch +import pytest +from django.conf import settings +from django.test import override_settings +from django.urls import reverse +from django.utils import timezone +from freezegun import freeze_time +from mock import Mock from rest_framework import status +from constance.test import override_config + +from kobo.apps.openrosa.apps.logger.models import Instance +from kobo.apps.openrosa.apps.logger.xform_instance_parser import add_uuid_prefix +from kobo.apps.organizations.constants import UsageType from kobo.apps.subsequences.models import SubmissionSupplement from kobo.apps.subsequences.tests.api.v2.base import SubsequenceBaseTestCase from kobo.apps.subsequences.tests.constants import QUESTION_SUPPLEMENT +from kpi.utils.xml import fromstring_preserve_root_xmlns, edit_submission_xml, xml_tostring class SubmissionSupplementAPITestCase(SubsequenceBaseTestCase): + def setUp(self): + super().setUp() + self.set_asset_advanced_features( + { + '_version': '20250820', + '_actionConfigs': { + 'q1': { + 'manual_transcription': [ + {'language': 'en'}, + ] + } + }, + } + ) + + def test_get_submission_with_nonexistent_instance_404s(self): + non_existent_supplement_details_url = reverse( + self._get_endpoint('submission-supplement'), + args=[self.asset.uid, 'bad-uuid'], + ) + rr = self.client.get(non_existent_supplement_details_url) + assert rr.status_code == 404 + + def test_patch_submission_with_nonexistent_instance_404s(self): + payload = { + '_version': '20250820', + 'q1': { + 'manual_transcription': { + 'language': 'en', + 'value': 'Hello world', + } + }, + } + non_existent_supplement_details_url = reverse( + self._get_endpoint('submission-supplement'), + args=[self.asset.uid, 'bad-uuid'], + ) + rr = self.client.patch(non_existent_supplement_details_url) + assert rr.status_code == 404 + + def test_get_submission_after_edit(self): + # Simulate edit + instance = Instance.objects.only('pk').get(root_uuid=self.submission_uuid) + deployment = self.asset.deployment + new_uuid = str(uuid.uuid4()) + xml_parsed = fromstring_preserve_root_xmlns(instance.xml) + edit_submission_xml( + xml_parsed, + deployment.SUBMISSION_DEPRECATED_UUID_XPATH, + add_uuid_prefix(self.submission_uuid), + ) + edit_submission_xml( + xml_parsed, + deployment.SUBMISSION_ROOT_UUID_XPATH, + add_uuid_prefix(instance.root_uuid), + ) + edit_submission_xml( + xml_parsed, + deployment.SUBMISSION_CURRENT_UUID_XPATH, + add_uuid_prefix(new_uuid), + ) + instance.xml = xml_tostring(xml_parsed) + instance.uuid = new_uuid + instance.save() + assert instance.root_uuid == self.submission_uuid + + # Retrieve advanced submission schema for edited submission + rr = self.client.get(self.supplement_details_url) + assert rr.status_code == status.HTTP_200_OK + + def test_get_submission_with_null_root_uuid(self): + # Simulate an old submission (never edited) where `root_uuid` was not yet set + Instance.objects.filter(root_uuid=self.submission_uuid).update(root_uuid=None) + rr = self.client.get(self.supplement_details_url) + assert rr.status_code == status.HTTP_200_OK + + def test_asset_post_submission_extra_with_transcript(self): + payload = { + '_version': '20250820', + 'q1': { + 'manual_transcription': { + 'language': 'en', + 'value': 'Hello world', + } + }, + } + now = timezone.now() + now_iso = now.isoformat().replace('+00:00','Z') + with freeze_time(now): + with patch('kobo.apps.subsequences.actions.base.uuid.uuid4', return_value='uuid1'): + response = self.client.patch( + self.supplement_details_url, data=payload, format='json' + ) + assert response.status_code == status.HTTP_200_OK + + expected_data = {'_version': '20250820', + 'q1': {'manual_transcription': {'_dateCreated': now_iso, + '_dateModified': now_iso, + '_versions': [{'_dateAccepted': now_iso, + '_dateCreated': now_iso, + '_uuid': 'uuid1', + 'language': 'en', + 'value': 'Hello world'}]}}} + assert response.data == expected_data + + def test_change_language_list(self): + field = self.txi.revise_field({ + 'tx1': { + 'value': 'T1', + 'dateModified': 'A', + 'dateCreated': 'A', + 'revisions': [] + } + }, { + 'tx2': { + 'value': 'T2', + } + }) + self.set_asset_advanced_features({ + 'translation': { + 'languages': [ + 'tx1', 'tx3' + ] + } + }) + resp = self.client.get(self.asset_url) + schema = resp.json()['advanced_submission_schema'] + package = { + 'submission': self.submission_uuid, + 'q1': { + 'transcript': { + 'value': 'they said hello', + }, + }, + } + # validate(package, schema) + + + +class SubmissionSupplementAPIValidationTestCase(SubsequenceBaseTestCase): def test_cannot_patch_if_action_is_invalid(self): payload = { @@ -258,3 +412,48 @@ def test_cannot_request_translation_without_transcription(self): ) assert response.status_code == status.HTTP_400_BAD_REQUEST assert 'Cannot translate without transcription' in str(response.data) + +class SubmissionSupplementAPIUsageLimitsTestCase(SubsequenceBaseTestCase): + def setUp(self): + super().setUp() + self.set_asset_advanced_features( + { + '_version': '20250820', + '_actionConfigs': { + 'q1': { + 'automatic_google_transcription': [ + {'language': 'en'}, + ], + 'automatic_google_translation': [ + {'language': 'es'}, + ] + } + }, + } + ) + + @pytest.mark.skipif( + not settings.STRIPE_ENABLED, reason='Requires stripe functionality' + ) + def test_google_services_usage_limit_checks(self): + payload = { + '_version': '20250820', + 'q1': { + 'automatic_google_transcription': { + 'language': 'en', + 'status': 'requested', + } + }, + } + mock_balances = { + UsageType.ASR_SECONDS: {'exceeded': True}, + UsageType.MT_CHARACTERS: {'exceeded': True}, + } + with patch( + 'kobo.apps.subsequences.api_view.ServiceUsageCalculator.get_usage_balances', + return_value=mock_balances, + ): + response = self.client.patch( + self.supplement_details_url, data=payload, format='json' + ) + assert response.status_code == status.HTTP_402_PAYMENT_REQUIRED diff --git a/kobo/apps/subsequences/tests/api/v2/test_permissions.py b/kobo/apps/subsequences/tests/api/v2/test_permissions.py index 603a51128c..b017ff857a 100644 --- a/kobo/apps/subsequences/tests/api/v2/test_permissions.py +++ b/kobo/apps/subsequences/tests/api/v2/test_permissions.py @@ -93,7 +93,7 @@ def test_can_read(self, username, shared, status_code): False, status.HTTP_404_NOT_FOUND, ), - # regular user with view permission + # regular user with change permission ( 'anotheruser', True, @@ -105,7 +105,7 @@ def test_can_read(self, username, shared, status_code): False, status.HTTP_200_OK, ), - # admin user with view permissions + # admin user with change permissions ( 'adminuser', True, From f4e86d1cb4254e0dbb9d02f49993d4772c065f1b Mon Sep 17 00:00:00 2001 From: rgraber Date: Wed, 5 Nov 2025 15:24:16 -0500 Subject: [PATCH 2/4] fixup!: cleanup --- kobo/apps/subsequences/actions/base.py | 3 +- kobo/apps/subsequences/models.py | 7 +- .../subsequences/tests/api/v2/test_api.py | 142 +++++++++--------- .../test_automatic_google_translation.py | 4 +- kobo/apps/subsequences/tests/test_models.py | 50 ------ 5 files changed, 80 insertions(+), 126 deletions(-) diff --git a/kobo/apps/subsequences/actions/base.py b/kobo/apps/subsequences/actions/base.py index 5c07434173..4880264e76 100644 --- a/kobo/apps/subsequences/actions/base.py +++ b/kobo/apps/subsequences/actions/base.py @@ -366,7 +366,6 @@ def revise_data( `submission` argument for future use by subclasses this method might need to be made more friendly for overriding """ - self.validate_data(action_data) self.raise_for_any_leading_underscore_key(action_data) @@ -611,7 +610,7 @@ def external_data_schema(self) -> dict: Schema rules: - The field `status` is always required and must be one of: - ["requested", "in_progress", "complete", "failed"]. + ["deleted", "in_progress", "complete", "failed"]. - If `status` == "complete": * The field `value` becomes required and must be a string. - If `status` == "failed": diff --git a/kobo/apps/subsequences/models.py b/kobo/apps/subsequences/models.py index d66e97f25f..1b3817fddf 100644 --- a/kobo/apps/subsequences/models.py +++ b/kobo/apps/subsequences/models.py @@ -1,9 +1,10 @@ +from constance import config from django.db import models from kobo.apps.openrosa.apps.logger.xform_instance_parser import remove_uuid_prefix from kpi.models.abstract_models import AbstractTimeStampedModel from .actions import ACTION_IDS_TO_CLASSES -from .constants import SUBMISSION_UUID_FIELD, SCHEMA_VERSIONS +from .constants import SCHEMA_VERSIONS, SUBMISSION_UUID_FIELD from .exceptions import InvalidAction, InvalidXPath from .schemas import validate_submission_supplement @@ -83,7 +84,9 @@ def revise_data(asset: 'kpi.Asset', submission: dict, incoming_data: dict) -> di raise InvalidAction from e action = action_class(question_xpath, action_params, asset) - action.check_limits(asset.owner) + + if config.USAGE_LIMIT_ENFORCEMENT: + action.check_limits(asset.owner) question_supplemental_data = supplemental_data.setdefault( question_xpath, {} diff --git a/kobo/apps/subsequences/tests/api/v2/test_api.py b/kobo/apps/subsequences/tests/api/v2/test_api.py index 6da65688e0..89fe22fa01 100644 --- a/kobo/apps/subsequences/tests/api/v2/test_api.py +++ b/kobo/apps/subsequences/tests/api/v2/test_api.py @@ -2,23 +2,28 @@ from unittest.mock import MagicMock, patch import pytest +from constance.test import override_config +from ddt import data, ddt, unpack from django.conf import settings -from django.test import override_settings from django.urls import reverse from django.utils import timezone from freezegun import freeze_time -from mock import Mock from rest_framework import status -from constance.test import override_config - from kobo.apps.openrosa.apps.logger.models import Instance from kobo.apps.openrosa.apps.logger.xform_instance_parser import add_uuid_prefix from kobo.apps.organizations.constants import UsageType +from kobo.apps.subsequences.actions.automatic_google_transcription import ( + AutomaticGoogleTranscriptionAction, +) from kobo.apps.subsequences.models import SubmissionSupplement from kobo.apps.subsequences.tests.api.v2.base import SubsequenceBaseTestCase from kobo.apps.subsequences.tests.constants import QUESTION_SUPPLEMENT -from kpi.utils.xml import fromstring_preserve_root_xmlns, edit_submission_xml, xml_tostring +from kpi.utils.xml import ( + edit_submission_xml, + fromstring_preserve_root_xmlns, + xml_tostring, +) class SubmissionSupplementAPITestCase(SubsequenceBaseTestCase): @@ -109,61 +114,40 @@ def test_asset_post_submission_extra_with_transcript(self): }, } now = timezone.now() - now_iso = now.isoformat().replace('+00:00','Z') + now_iso = now.isoformat().replace('+00:00', 'Z') with freeze_time(now): - with patch('kobo.apps.subsequences.actions.base.uuid.uuid4', return_value='uuid1'): + with patch( + 'kobo.apps.subsequences.actions.base.uuid.uuid4', return_value='uuid1' + ): response = self.client.patch( self.supplement_details_url, data=payload, format='json' ) assert response.status_code == status.HTTP_200_OK - expected_data = {'_version': '20250820', - 'q1': {'manual_transcription': {'_dateCreated': now_iso, - '_dateModified': now_iso, - '_versions': [{'_dateAccepted': now_iso, - '_dateCreated': now_iso, - '_uuid': 'uuid1', - 'language': 'en', - 'value': 'Hello world'}]}}} - assert response.data == expected_data - - def test_change_language_list(self): - field = self.txi.revise_field({ - 'tx1': { - 'value': 'T1', - 'dateModified': 'A', - 'dateCreated': 'A', - 'revisions': [] - } - }, { - 'tx2': { - 'value': 'T2', - } - }) - self.set_asset_advanced_features({ - 'translation': { - 'languages': [ - 'tx1', 'tx3' - ] - } - }) - resp = self.client.get(self.asset_url) - schema = resp.json()['advanced_submission_schema'] - package = { - 'submission': self.submission_uuid, + expected_data = { + '_version': '20250820', 'q1': { - 'transcript': { - 'value': 'they said hello', - }, + 'manual_transcription': { + '_dateCreated': now_iso, + '_dateModified': now_iso, + '_versions': [ + { + '_dateAccepted': now_iso, + '_dateCreated': now_iso, + '_uuid': 'uuid1', + 'language': 'en', + 'value': 'Hello world', + } + ], + } }, } - # validate(package, schema) - + assert response.data == expected_data class SubmissionSupplementAPIValidationTestCase(SubsequenceBaseTestCase): - def test_cannot_patch_if_action_is_invalid(self): + def test_cannot_patch_if_question_has_no_configured_actions(self): payload = { '_version': '20250820', 'q1': { @@ -174,14 +158,24 @@ def test_cannot_patch_if_action_is_invalid(self): }, } - # No actions activated at the asset level + # No actions activated at the asset level for any questions response = self.client.patch( self.supplement_details_url, data=payload, format='json' ) assert response.status_code == status.HTTP_400_BAD_REQUEST - assert 'Invalid action' in str(response.data) + assert 'Invalid question' in str(response.data) - # Activate manual transcription (even if payload asks for translation) + def test_cannot_patch_if_action_is_invalid(self): + # Activate manual transcription (even though payload asks for translation) + payload = { + '_version': '20250820', + 'q1': { + 'manual_translation': { + 'language': 'es', + 'value': 'buenas noches', + } + }, + } self.set_asset_advanced_features( { '_version': '20250820', @@ -242,17 +236,14 @@ def test_cannot_set_value_with_automatic_actions(self): ], 'automatic_google_translation': [ {'language': 'fr'}, - ] + ], } }, } self.set_asset_advanced_features(advanced_features) # Simulate a completed transcription, first. - mock_submission_supplement = { - '_version': '20250820', - 'q1': QUESTION_SUPPLEMENT - } + mock_submission_supplement = {'_version': '20250820', 'q1': QUESTION_SUPPLEMENT} SubmissionSupplement.objects.create( submission_uuid=self.submission_uuid, @@ -276,7 +267,6 @@ def test_cannot_set_value_with_automatic_actions(self): assert response.status_code == status.HTTP_400_BAD_REQUEST assert 'Invalid payload' in str(response.data) - def test_cannot_accept_incomplete_automatic_transcription(self): # Set up the asset to allow automatic google transcription self.set_asset_advanced_features( @@ -329,17 +319,14 @@ def test_cannot_accept_incomplete_automatic_translation(self): ], 'automatic_google_translation': [ {'language': 'fr'}, - ] + ], } }, } ) # Simulate a completed transcription, first. - mock_submission_supplement = { - '_version': '20250820', - 'q1': QUESTION_SUPPLEMENT - } + mock_submission_supplement = {'_version': '20250820', 'q1': QUESTION_SUPPLEMENT} SubmissionSupplement.objects.create( submission_uuid=self.submission_uuid, content=mock_submission_supplement, @@ -383,7 +370,7 @@ def test_cannot_request_translation_without_transcription(self): ], 'automatic_google_translation': [ {'language': 'fr'}, - ] + ], } }, } @@ -413,6 +400,8 @@ def test_cannot_request_translation_without_transcription(self): assert response.status_code == status.HTTP_400_BAD_REQUEST assert 'Cannot translate without transcription' in str(response.data) + +@ddt class SubmissionSupplementAPIUsageLimitsTestCase(SubsequenceBaseTestCase): def setUp(self): super().setUp() @@ -426,7 +415,7 @@ def setUp(self): ], 'automatic_google_translation': [ {'language': 'es'}, - ] + ], } }, } @@ -435,13 +424,19 @@ def setUp(self): @pytest.mark.skipif( not settings.STRIPE_ENABLED, reason='Requires stripe functionality' ) - def test_google_services_usage_limit_checks(self): + @data( + (True, status.HTTP_402_PAYMENT_REQUIRED), + (False, status.HTTP_200_OK), + ) + @unpack + def test_google_services_usage_limit_checks( + self, usage_limit_enforcement, expected_result_code + ): payload = { '_version': '20250820', 'q1': { 'automatic_google_transcription': { 'language': 'en', - 'status': 'requested', } }, } @@ -449,11 +444,18 @@ def test_google_services_usage_limit_checks(self): UsageType.ASR_SECONDS: {'exceeded': True}, UsageType.MT_CHARACTERS: {'exceeded': True}, } + with patch( - 'kobo.apps.subsequences.api_view.ServiceUsageCalculator.get_usage_balances', + 'kobo.apps.subsequences.actions.base.ServiceUsageCalculator.get_usage_balances', return_value=mock_balances, ): - response = self.client.patch( - self.supplement_details_url, data=payload, format='json' - ) - assert response.status_code == status.HTTP_402_PAYMENT_REQUIRED + with override_config(USAGE_LIMIT_ENFORCEMENT=usage_limit_enforcement): + with patch.object( + AutomaticGoogleTranscriptionAction, + 'run_external_process', + return_value=None, # noqa + ): + response = self.client.patch( + self.supplement_details_url, data=payload, format='json' + ) + assert response.status_code == expected_result_code diff --git a/kobo/apps/subsequences/tests/test_automatic_google_translation.py b/kobo/apps/subsequences/tests/test_automatic_google_translation.py index c596c0561d..f89a93a775 100644 --- a/kobo/apps/subsequences/tests/test_automatic_google_translation.py +++ b/kobo/apps/subsequences/tests/test_automatic_google_translation.py @@ -6,8 +6,8 @@ import pytest from ..actions.automatic_google_translation import AutomaticGoogleTranslationAction -from .constants import EMPTY_SUBMISSION, EMPTY_SUPPLEMENT, QUESTION_SUPPLEMENT from ..exceptions import TranscriptionNotFound +from .constants import EMPTY_SUBMISSION, EMPTY_SUPPLEMENT, QUESTION_SUPPLEMENT def test_valid_params_pass_validation(): @@ -405,7 +405,7 @@ def test_action_is_updated_in_background_if_in_progress(): ): mock_service.process_data.return_value = {'status': 'in_progress'} with patch( - 'kobo.apps.subsequences.actions.base.poll_run_automatic_process' + 'kobo.apps.subsequences.actions.base.poll_run_external_process' ) as task_mock: action.revise_data( submission, EMPTY_SUPPLEMENT, {'language': 'fr'} diff --git a/kobo/apps/subsequences/tests/test_models.py b/kobo/apps/subsequences/tests/test_models.py index 6e49e1c530..c9148351e8 100644 --- a/kobo/apps/subsequences/tests/test_models.py +++ b/kobo/apps/subsequences/tests/test_models.py @@ -150,56 +150,6 @@ def test_retrieve_data_with_stale_questions(self): ) assert submission_supplement == EMPTY_SUPPLEMENT - def test_retrieve_data_from_migrated_data(self): - submission_supplement = { - 'group_name/question_name': { - 'transcript': { - 'languageCode': 'ar', - 'value': 'فارغ', - 'dateCreated': '2024-04-08T15:27:00Z', - 'dateModified': '2024-04-08T15:31:00Z', - 'revisions': [ - { - 'languageCode': 'ar', - 'value': 'هائج', - 'dateModified': '2024-04-08T15:27:00Z', - } - ], - }, - 'translation': [ - { - 'languageCode': 'en', - 'value': 'berserk', - 'dateCreated': '2024-04-08T15:27:00Z', - 'dateModified': '2024-04-08T15:27:00Z', - }, - { - 'languageCode': 'es', - 'value': 'enloquecido', - 'dateCreated': '2024-04-08T15:29:00Z', - 'dateModified': '2024-04-08T15:32:00Z', - 'revisions': [ - { - 'languageCode': 'es', - 'value': 'loco', - 'dateModified': '2024-04-08T15:29:00Z', - } - ], - }, - ], - }, - } - - SubmissionSupplement.objects.create( - asset=self.asset, - submission_uuid=self.submission_root_uuid, - content=submission_supplement, - ) - submission_supplement = SubmissionSupplement.retrieve_data( - self.asset, submission_root_uuid=self.submission_root_uuid - ) - assert submission_supplement == self.EXPECTED_SUBMISSION_SUPPLEMENT - def test_retrieve_data_with_submission_root_uuid(self): self.test_revise_data() submission_supplement = SubmissionSupplement.retrieve_data( From 45a477367d13de0a967f43580d0474b4641d8b0e Mon Sep 17 00:00:00 2001 From: rgraber Date: Wed, 5 Nov 2025 15:32:12 -0500 Subject: [PATCH 3/4] fixup!: remove unwanted test --- kobo/apps/subsequences/tests/test_models.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/kobo/apps/subsequences/tests/test_models.py b/kobo/apps/subsequences/tests/test_models.py index c9148351e8..53c3d8d753 100644 --- a/kobo/apps/subsequences/tests/test_models.py +++ b/kobo/apps/subsequences/tests/test_models.py @@ -136,20 +136,6 @@ def test_retrieve_data_with_invalid_arguments(self): self.asset, submission_root_uuid=None, prefetched_supplement=None ) - def test_retrieve_data_with_stale_questions(self): - SubmissionSupplement.objects.create( - asset=self.asset, - submission_uuid=self.submission_root_uuid, - content=self.EXPECTED_SUBMISSION_SUPPLEMENT, - ) - advanced_features = deepcopy(self.ADVANCED_FEATURES) - config = advanced_features['_actionConfigs'].pop('group_name/question_name') - advanced_features['_actionConfigs']['group_name/renamed_question_name'] = config - submission_supplement = SubmissionSupplement.retrieve_data( - self.asset, self.submission_root_uuid - ) - assert submission_supplement == EMPTY_SUPPLEMENT - def test_retrieve_data_with_submission_root_uuid(self): self.test_revise_data() submission_supplement = SubmissionSupplement.retrieve_data( From 2797605cd802a55f977fbb043fde8f0f741a87d3 Mon Sep 17 00:00:00 2001 From: rgraber Date: Wed, 5 Nov 2025 15:36:01 -0500 Subject: [PATCH 4/4] fixup!: format --- kobo/apps/subsequences/tests/api/v2/test_api.py | 6 ++++-- kobo/apps/subsequences/tests/test_models.py | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/kobo/apps/subsequences/tests/api/v2/test_api.py b/kobo/apps/subsequences/tests/api/v2/test_api.py index 89fe22fa01..30c11a344d 100644 --- a/kobo/apps/subsequences/tests/api/v2/test_api.py +++ b/kobo/apps/subsequences/tests/api/v2/test_api.py @@ -64,7 +64,9 @@ def test_patch_submission_with_nonexistent_instance_404s(self): self._get_endpoint('submission-supplement'), args=[self.asset.uid, 'bad-uuid'], ) - rr = self.client.patch(non_existent_supplement_details_url) + rr = self.client.patch( + non_existent_supplement_details_url, data=payload, format='json' + ) assert rr.status_code == 404 def test_get_submission_after_edit(self): @@ -446,7 +448,7 @@ def test_google_services_usage_limit_checks( } with patch( - 'kobo.apps.subsequences.actions.base.ServiceUsageCalculator.get_usage_balances', + 'kobo.apps.subsequences.actions.base.ServiceUsageCalculator.get_usage_balances', # noqa return_value=mock_balances, ): with override_config(USAGE_LIMIT_ENFORCEMENT=usage_limit_enforcement): diff --git a/kobo/apps/subsequences/tests/test_models.py b/kobo/apps/subsequences/tests/test_models.py index 53c3d8d753..e20ece60bf 100644 --- a/kobo/apps/subsequences/tests/test_models.py +++ b/kobo/apps/subsequences/tests/test_models.py @@ -1,5 +1,4 @@ import uuid -from copy import deepcopy from datetime import datetime from unittest.mock import patch from zoneinfo import ZoneInfo