diff --git a/.github/workflows/bf-py-coding-style.yml b/.github/workflows/bf-py-coding-style.yml index d00ef3b9c7..90da2fd6a7 100644 --- a/.github/workflows/bf-py-coding-style.yml +++ b/.github/workflows/bf-py-coding-style.yml @@ -20,10 +20,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup Python ${{ inputs.python-version }} + - name: Setup Python 3.12 uses: actions/setup-python@v5 with: - python-version: ${{ inputs.python-version }} + python-version: '3.12' - name: Run pre-commit (benefit) uses: pre-commit/action@v3.0.1 with: diff --git a/.github/workflows/bf-pytest.yml b/.github/workflows/bf-pytest.yml index ae261ad3bd..92607ad392 100644 --- a/.github/workflows/bf-pytest.yml +++ b/.github/workflows/bf-pytest.yml @@ -19,7 +19,7 @@ jobs: services: postgres: - image: postgres:13 + image: postgres:17 ports: - 5432:5432 options: >- @@ -45,10 +45,10 @@ jobs: sudo apt-get update sudo apt-get install libpq-dev wkhtmltopdf gettext - - name: Setup Python 3.9 + - name: Setup Python 3.12 uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: 3.12 cache: pip - name: Cache pip packages diff --git a/backend/benefit/applications/api/v1/search_views.py b/backend/benefit/applications/api/v1/search_views.py index 6b97c26614..24d6f82a68 100755 --- a/backend/benefit/applications/api/v1/search_views.py +++ b/backend/benefit/applications/api/v1/search_views.py @@ -1,5 +1,4 @@ import re -from datetime import datetime from typing import Union from dateutil.relativedelta import relativedelta @@ -11,6 +10,7 @@ ) from django.db import models from django.db.models import Q +from django.utils import timezone from django.utils.translation import gettext_lazy as _ from fuzzywuzzy import fuzz from rest_framework import filters as drf_filters @@ -113,13 +113,13 @@ def _prepare_application_queryset(archived, subsidy_in_effect, years_since_decis if subsidy_in_effect and subsidy_in_effect == SubsidyInEffect.NOW: queryset = queryset.filter( status=ApplicationStatus.ACCEPTED, - calculation__start_date__lte=datetime.now(), - calculation__end_date__gte=datetime.now(), + calculation__start_date__lte=timezone.now(), + calculation__end_date__gte=timezone.now(), ) elif subsidy_in_effect and subsidy_in_effect.isnumeric(): queryset = queryset.filter( status=ApplicationStatus.ACCEPTED, - calculation__end_date__gte=datetime.now().date() + calculation__end_date__gte=timezone.now().date() - relativedelta(years=int(subsidy_in_effect)), ) if years_since_decision: @@ -127,12 +127,12 @@ def _prepare_application_queryset(archived, subsidy_in_effect, years_since_decis Q( status=ApplicationStatus.ACCEPTED, batch__isnull=False, - batch__decision_date__gte=datetime.now() + batch__decision_date__gte=timezone.now() - relativedelta(years=years_since_decision), ) | Q( status=ApplicationStatus.REJECTED, - handled_at__gte=datetime.now() + handled_at__gte=timezone.now() - relativedelta(years=years_since_decision), ), ) @@ -143,17 +143,17 @@ def _prepare_archival_application_queryset(subsidy_in_effect, years_since_decisi queryset = ArchivalApplication.objects if subsidy_in_effect and subsidy_in_effect == SubsidyInEffect.NOW: return queryset.filter( - start_date__lte=datetime.now(), - end_date__gte=datetime.now(), + start_date__lte=timezone.now(), + end_date__gte=timezone.now(), ) elif subsidy_in_effect: return queryset.filter( - end_date__gte=datetime.now().date() + end_date__gte=timezone.now().date() - relativedelta(years=int(subsidy_in_effect)) ) elif years_since_decision: return queryset.filter( - end_date__gte=datetime.now().date() + end_date__gte=timezone.now().date() - relativedelta(years=int(years_since_decision)) ) return queryset.all() diff --git a/backend/benefit/applications/benefit_aggregation.py b/backend/benefit/applications/benefit_aggregation.py index a52341566a..7ee6fc1bfd 100644 --- a/backend/benefit/applications/benefit_aggregation.py +++ b/backend/benefit/applications/benefit_aggregation.py @@ -2,7 +2,6 @@ from dataclasses import dataclass, field from decimal import Decimal from itertools import chain -from typing import List, Optional, Union from dateutil.relativedelta import relativedelta from django.utils.text import format_lazy @@ -16,11 +15,11 @@ @dataclass class FormerBenefitInfo: - warnings: List[str] = field(default_factory=list) - months_used: Optional[Decimal] = ( - None # None is used if employee info is not entered yet - ) - months_remaining: Optional[Decimal] = None # None means that there is no limit + warnings: list[str] = field(default_factory=list) + months_used: None | ( + Decimal + ) = None # None is used if employee info is not entered yet + months_remaining: Decimal | None = None # None means that there is no limit # apprenticeship is a special case @@ -83,7 +82,7 @@ def get_former_benefit_info( def _get_past_benefits( application, company, social_security_number, end_date -) -> List[Union[PreviousBenefit, Application]]: +) -> list[PreviousBenefit | Application]: """ Return a list containing two types of objects: * PreviousBenefits diff --git a/backend/benefit/applications/models.py b/backend/benefit/applications/models.py index 3c08a62cd8..25107b6306 100755 --- a/backend/benefit/applications/models.py +++ b/backend/benefit/applications/models.py @@ -1,6 +1,5 @@ import re from datetime import date, timedelta -from typing import List from dateutil.relativedelta import relativedelta from django.conf import settings @@ -189,8 +188,8 @@ def with_due_instalments(self, status: InstalmentStatus): def get_by_statuses( self, - application_statuses: List[ApplicationStatus], - ahjo_statuses: List[AhjoStatusEnum], + application_statuses: list[ApplicationStatus], + ahjo_statuses: list[AhjoStatusEnum], has_no_case_id: bool, retry_failed_older_than_hours: int = 0, retry_status: AhjoStatusEnum = None, @@ -264,8 +263,8 @@ def get_by_statuses( def get_for_ahjo_decision( self, - application_statuses: List[ApplicationStatus], - ahjo_statuses: List[AhjoStatusEnum], + application_statuses: list[ApplicationStatus], + ahjo_statuses: list[AhjoStatusEnum], has_no_case_id: bool = False, retry_failed_older_than_hours: int = 0, retry_status: AhjoStatusEnum = None, @@ -710,7 +709,7 @@ class DeMinimisAid(UUIDModel, TimeStampedModel): ) def __str__(self): - return "{}: {} {}".format(self.pk, self.application.pk, self.granter) + return f"{self.pk}: {self.application.pk} {self.granter}" class Meta: db_table = "bf_applications_deminimisaid" @@ -1135,7 +1134,7 @@ def birthday(self): return social_security_number_birthdate(self.social_security_number) def __str__(self): - return "{} {} ({})".format(self.first_name, self.last_name, self.email) + return f"{self.first_name} {self.last_name} ({self.email})" class Meta: db_table = "bf_applications_employee" @@ -1195,7 +1194,7 @@ class Meta: ordering = ["application__created_at", "attachment_type", "created_at"] def __str__(self): - return "{} {}".format(self.attachment_type, self.attachment_file.name) + return f"{self.attachment_type} {self.attachment_file.name}" class ReviewState(models.Model): diff --git a/backend/benefit/applications/tests/conftest.py b/backend/benefit/applications/tests/conftest.py old mode 100755 new mode 100644 index 7228c779a0..7734bc83b9 --- a/backend/benefit/applications/tests/conftest.py +++ b/backend/benefit/applications/tests/conftest.py @@ -1,12 +1,11 @@ import os import random import uuid -from datetime import date, datetime, timedelta +from datetime import date, timedelta import factory import pytest from django.conf import settings -from django.db import connection from django.utils import timezone from applications.enums import ( @@ -66,14 +65,6 @@ from terms.tests.factories import TermsOfServiceApprovalFactory -@pytest.fixture(scope="session", autouse=True) -def django_db_setup(django_db_setup, django_db_blocker): - """Test session DB setup.""" - with django_db_blocker.unblock(): - with connection.cursor() as cursor: - cursor.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm;") - - @pytest.fixture def anonymous_application(): with factory.Faker.override_default_locale("fi_FI"): @@ -330,11 +321,6 @@ def drafts_about_to_be_deleted(draft_notification_date, draft_applications): yield draft_applications -@pytest.fixture(autouse=True) -def auto_accept_tos(autouse_django_db, accept_tos): - return accept_tos - - @pytest.fixture() def set_debug_to_true(settings): settings.DEBUG = True @@ -435,7 +421,7 @@ def ahjo_open_case_top_level_dict(decided_application): return { "Title": "a" * 512, - "Acquired": application.created_at.isoformat(), + "Acquired": application.created_at.isoformat("T", "seconds"), "ClassificationCode": "02 05 01 00", "ClassificationTitle": "Kunnan myöntämät avustukset", "Language": language, @@ -514,15 +500,15 @@ def _delete_attachments(application: Application): def pytest_sessionfinish(session, exitstatus): - # Delete all files in the media folder - files_in_media = os.listdir(settings.MEDIA_ROOT) - number_of_files = len(files_in_media) - for file in files_in_media: - try: + try: + # Delete all files in the media folder + files_in_media = os.listdir(settings.MEDIA_ROOT) + number_of_files = len(files_in_media) + for file in files_in_media: os.remove(os.path.join(settings.MEDIA_ROOT, file)) - except OSError as e: - print(f"Error while deleting file in media folder: {e}") # noqa: T201 - print(f"\nTests finished, deleted {number_of_files} files in the media folder") # noqa: T201 + print(f"\nTests finished, deleted {number_of_files} files in the media folder") # noqa: T201 + except OSError as e: + print(f"Error while deleting file in media folder: {e}") # noqa: T201 @pytest.fixture @@ -897,7 +883,7 @@ def decision_details(): decision_maker_name="Test Test", decision_maker_title="Test Title", section_of_the_law="16 §", - decision_date=datetime.now(), + decision_date=timezone.now(), ) @@ -1293,5 +1279,5 @@ def non_expired_token(): access_token="access_token", refresh_token="refresh_token", expires_in=30000, - created_at=datetime.now(timezone.utc), + created_at=timezone.now(), ) diff --git a/backend/benefit/applications/tests/factories.py b/backend/benefit/applications/tests/factories.py index b0099f63f4..c7482ba297 100755 --- a/backend/benefit/applications/tests/factories.py +++ b/backend/benefit/applications/tests/factories.py @@ -129,7 +129,8 @@ class ApplicationFactory(factory.django.DjangoModelFactory): benefit_type = BenefitType.EMPLOYMENT_BENEFIT start_date = factory.Faker( "date_between_dates", - date_start=date(date.today().year, 1, 1), + # Start must not be more than 4 months old or validation errors might happen + date_start=date.today() - timedelta(days=90), date_end=date.today() + timedelta(days=100), ) end_date = factory.LazyAttribute( @@ -160,6 +161,7 @@ def bases(self, created, extracted, **kwargs): class Meta: model = Application + skip_postgeneration_save = True attachment_factory_string = "applications.tests.factories.AttachmentFactory" @@ -229,6 +231,7 @@ def calculation(self, created, extracted, **kwargs): self.calculation = Calculation.objects.create_for_application(self, **kwargs) self.calculation.init_calculator() self.calculation.calculate() + self.save() class HandlingApplicationFactory(ReceivedApplicationFactory): @@ -342,7 +345,7 @@ class EmployeeFactory(factory.django.DjangoModelFactory): "random_element", elements=[v[0] for v in APPLICATION_LANGUAGE_CHOICES] ) job_title = factory.Faker("job", locale="fi_FI") - monthly_pay = factory.Faker("random_int", max=5000) + monthly_pay = factory.Faker("random_int", min=1, max=5000) vacation_money = factory.Faker("random_int", max=5000) other_expenses = factory.Faker("random_int", max=5000) working_hours = factory.Faker("random_int", min=18, max=40) @@ -370,6 +373,7 @@ class BaseApplicationBatchFactory(factory.django.DjangoModelFactory): class Meta: model = ApplicationBatch + skip_postgeneration_save = True class ApplicationBatchFactory(BaseApplicationBatchFactory): diff --git a/backend/benefit/applications/tests/test_ahjo_authentication.py b/backend/benefit/applications/tests/test_ahjo_authentication.py index 82824b2e88..961568b7db 100644 --- a/backend/benefit/applications/tests/test_ahjo_authentication.py +++ b/backend/benefit/applications/tests/test_ahjo_authentication.py @@ -73,6 +73,7 @@ def test_is_configured(ahjo_connector): assert ahjo_connector.is_configured() is False +@pytest.mark.django_db def test_get_new_token(ahjo_connector, ahjo_code, token_response): # Test with valid auth code with requests_mock.Mocker() as m: @@ -94,6 +95,7 @@ def test_get_new_token(ahjo_connector, ahjo_code, token_response): ahjo_connector.get_initial_token() +@pytest.mark.django_db def test_refresh_token(ahjo_connector, ahjo_setting, token_response): # Test with valid refresh token @@ -115,6 +117,7 @@ def test_refresh_token(ahjo_connector, ahjo_setting, token_response): ahjo_connector.refresh_token() +@pytest.mark.django_db def test_refresh_expired_token(ahjo_connector, ahjo_setting, token_response): # Test with valid refresh token ahjo_setting.created_at = datetime.now(timezone.utc) - timedelta(hours=22) @@ -129,6 +132,7 @@ def test_refresh_expired_token(ahjo_connector, ahjo_setting, token_response): ahjo_connector.refresh_token() +@pytest.mark.django_db def test_do_token_request(ahjo_connector: AhjoConnector, token_response): # Test with successful request @@ -149,6 +153,7 @@ def test_do_token_request(ahjo_connector: AhjoConnector, token_response): ahjo_connector.do_token_request({}) +@pytest.mark.django_db def test_get_token_from_db(ahjo_connector: AhjoConnector, ahjo_setting): token = ahjo_connector.get_token_from_db() assert isinstance(token, AhjoToken) @@ -172,6 +177,7 @@ def test_check_if_token_is_expired(expired_token, non_expired_token): assert non_expired_token.has_expired() is False +@pytest.mark.django_db def test_create_token(ahjo_connector: AhjoConnector, non_expired_token): # Test with new token token_to_create = AhjoToken( diff --git a/backend/benefit/applications/tests/test_ahjo_decision_proposal_drafts.py b/backend/benefit/applications/tests/test_ahjo_decision_proposal_drafts.py index a5bf068f33..2b8cfc58c4 100644 --- a/backend/benefit/applications/tests/test_ahjo_decision_proposal_drafts.py +++ b/backend/benefit/applications/tests/test_ahjo_decision_proposal_drafts.py @@ -106,6 +106,7 @@ def _prepare_calculation(application): ), ], ) +@pytest.mark.django_db def test_decision_proposal_drafting( application, handler_api_client, diff --git a/backend/benefit/applications/tests/test_ahjo_decisions.py b/backend/benefit/applications/tests/test_ahjo_decisions.py index df1a7fb53e..63aa858276 100644 --- a/backend/benefit/applications/tests/test_ahjo_decisions.py +++ b/backend/benefit/applications/tests/test_ahjo_decisions.py @@ -11,6 +11,7 @@ ) +@pytest.mark.django_db def test_replace_accepted_decision_template_placeholders( decided_application, accepted_ahjo_decision_section ): @@ -27,6 +28,7 @@ def test_replace_accepted_decision_template_placeholders( assert f"{wanted_start_date} - {wanted_end_date}" in replaced_template +@pytest.mark.django_db def test_replace_denied_decision_template_placeholders( decided_application, denied_ahjo_decision_section ): @@ -39,6 +41,7 @@ def test_replace_denied_decision_template_placeholders( assert decided_application.company.name in replaced_template +@pytest.mark.django_db def test_anonymous_client_cannot_access_templates( anonymous_client, decided_application ): @@ -56,6 +59,7 @@ def test_anonymous_client_cannot_access_templates( (DecisionType.DENIED,), ], ) +@pytest.mark.django_db def test_handler_gets_the_template_sections( decided_application, handler_api_client, decision_type ): @@ -82,6 +86,7 @@ def get_decisions_detail_url(application_id: uuid, decision_text_id: uuid) -> st ) +@pytest.mark.django_db def test_decision_text_api_unauthenticated(anonymous_client, decided_application): url = get_decisions_list_url(decided_application.id) response = anonymous_client.post(url) @@ -136,6 +141,7 @@ def test_decision_text_api_post( (DecisionType.DENIED, "sv", 400), ], ) +@pytest.mark.django_db def test_decision_text_api_without_decision_maker_data( decided_application, handler_api_client, @@ -211,6 +217,7 @@ def test_decision_text_serializer_validation_errors(decision_type, language): assert serializer.errors["decision_maker_id"] == ["This field is required."] +@pytest.mark.django_db def test_decision_text_api_get(decided_application, handler_api_client): url = get_decisions_list_url(decided_application.id) decision_text = AhjoDecisionText.objects.create( @@ -226,6 +233,7 @@ def test_decision_text_api_get(decided_application, handler_api_client): assert response.data["language"] == decision_text.language +@pytest.mark.django_db def test_decision_text_api_put( decided_application, handler_api_client, fake_decisionmakers, fake_signers ): diff --git a/backend/benefit/applications/tests/test_ahjo_integration.py b/backend/benefit/applications/tests/test_ahjo_integration.py index 69b2a33a89..84c97ae661 100644 --- a/backend/benefit/applications/tests/test_ahjo_integration.py +++ b/backend/benefit/applications/tests/test_ahjo_integration.py @@ -52,7 +52,6 @@ from applications.tests.factories import ApplicationFactory, DecidedApplicationFactory from calculator.models import Calculation from calculator.tests.factories import PaySubsidyFactory -from common.tests.conftest import reseed from common.utils import hash_file from companies.tests.factories import CompanyFactory from helsinkibenefit.tests.conftest import * # noqa @@ -66,14 +65,6 @@ ) -@pytest.fixture(autouse=True) -def run_before_and_after_tests(): - from applications.tests.before_after import before_test_reseed - - before_test_reseed([]) - yield - - def _assert_html_content(html, include_keys=(), excluded_keys=()): for k in include_keys: assert k in html @@ -102,6 +93,7 @@ def _assert_html_content(html, include_keys=(), excluded_keys=()): (YtjOrganizationCode.COMPANY_FORM_CODE_DEFAULT, "oy", True), ], ) +@pytest.mark.django_db @patch("applications.services.ahjo_integration.pdfkit.from_string") def test_generate_single_approved_template_html( mock_pdf_convert, @@ -144,6 +136,7 @@ def test_generate_single_approved_template_html( ) == should_show_de_minimis_aid_footer +@pytest.mark.django_db @patch("applications.services.ahjo_integration.pdfkit.from_string") def test_generate_single_declined_template_html(mock_pdf_convert): mock_pdf_convert.return_value = {} @@ -174,6 +167,7 @@ def test_generate_single_declined_template_html(mock_pdf_convert): ) +@pytest.mark.django_db @patch("applications.services.ahjo_integration.pdfkit.from_string") def test_generate_composed_template_html(mock_pdf_convert): mock_pdf_convert.return_value = {} @@ -239,9 +233,8 @@ def test_generate_composed_template_html(mock_pdf_convert): ) -# Test flaking if no reseed is used +@pytest.mark.django_db def test_export_application_batch(application_batch): - reseed(12345) application_batch.applications.add( DecidedApplicationFactory.create( status=ApplicationStatus.ACCEPTED, @@ -249,12 +242,10 @@ def test_export_application_batch(application_batch): ) ) - reseed(23456) application_batch.applications.add( DecidedApplicationFactory.create(status=ApplicationStatus.REJECTED) ) - reseed(34567) application_batch.applications.add( DecidedApplicationFactory.create(status=ApplicationStatus.CANCELLED) ) @@ -268,9 +259,9 @@ def test_export_application_batch(application_batch): ).count() + 4 ) - reseed(777) +@pytest.mark.django_db @patch("applications.services.ahjo_integration.pdfkit.from_string") def test_multiple_benefit_per_application(mock_pdf_convert): mock_pdf_convert.return_value = {} @@ -329,6 +320,7 @@ def test_multiple_benefit_per_application(mock_pdf_convert): ) +@pytest.mark.django_db def test_prepare_ahjo_file_response(decided_application): attachment = decided_application.attachments.first() response = AhjoAttachmentView._prepare_file_response(attachment) @@ -348,6 +340,7 @@ def attachment(decided_application): return decided_application.attachments.first() +@pytest.mark.django_db def test_get_attachment_success(ahjo_client, attachment, ahjo_user_token, settings): settings.NEXT_PUBLIC_MOCK_FLAG = True url = reverse("ahjo_attachment_url", kwargs={"uuid": attachment.id}) @@ -365,6 +358,7 @@ def test_get_attachment_success(ahjo_client, attachment, ahjo_user_token, settin ) +@pytest.mark.django_db def test_get_attachment_not_found(ahjo_client, ahjo_user_token, settings): settings.NEXT_PUBLIC_MOCK_FLAG = True id = uuid.uuid4() @@ -376,6 +370,7 @@ def test_get_attachment_not_found(ahjo_client, ahjo_user_token, settings): assert response.status_code == 404 +@pytest.mark.django_db def test_get_attachment_unauthorized_wrong_or_missing_credentials( anonymous_client, attachment, settings ): @@ -393,6 +388,7 @@ def test_get_attachment_unauthorized_wrong_or_missing_credentials( assert response.status_code == 401 +@pytest.mark.django_db def test_get_attachment_unauthorized_ip_not_allowed( ahjo_client, ahjo_user_token, attachment, settings ): @@ -552,6 +548,7 @@ def test_ahjo_callback_success( (AhjoDecisionUpdateType.REMOVED, AhjoStatusEnum.REMOVED_IN_AHJO), ], ) +@pytest.mark.django_db def test_subscribe_to_decisions_callback_success( ahjo_client, ahjo_user_token, @@ -680,6 +677,7 @@ def test_ahjo_callback_failure( (AhjoRequestType.DELETE_APPLICATION,), ], ) +@pytest.mark.django_db def test_ahjo_callback_unauthorized_wrong_or_missing_credentials( anonymous_client, decided_application, settings, request_type ): @@ -706,6 +704,7 @@ def test_ahjo_callback_unauthorized_wrong_or_missing_credentials( (AhjoRequestType.DELETE_APPLICATION,), ], ) +@pytest.mark.django_db def test_ahjo_callback_unauthorized_ip_not_allowed( ahjo_client, ahjo_user_token, decided_application, settings, request_type ): @@ -720,6 +719,7 @@ def test_ahjo_callback_unauthorized_ip_not_allowed( assert response.status_code == 403 +@pytest.mark.django_db def test_ahjo_callback_raises_error_without_batch( ahjo_callback_payload, decided_application, ahjo_client, ahjo_user_token, settings ): @@ -996,6 +996,7 @@ def test_generate_ahjo_secret_decision_text_xml(decided_application): ), ], ) +@pytest.mark.django_db def test_ahjo_query_parameter_resolver( ahjo_request_type, query_parameters, retry_failed_older_than_hours ): @@ -1212,6 +1213,7 @@ def test_get_applications_for_update_request( (ApplicationStatus.RECEIVED, AhjoStatusEnum.DELETE_REQUEST_SENT, 1), ], ) +@pytest.mark.django_db def test_get_applications_for_delete_request( handling_application, application_status, @@ -1601,6 +1603,7 @@ def test_retry_get_applications_for_ahjo_decision_proposal_request( ), ], ) +@pytest.mark.django_db def test_get_applications_for_ahjo_details_request( decided_application, application_status, @@ -1628,6 +1631,7 @@ def test_get_applications_for_ahjo_details_request( (ApplicationStatus.REJECTED, AhjoStatusEnum.DECISION_DETAILS_REQUEST_SENT, 1), ], ) +@pytest.mark.django_db def test_retry_get_applications_for_ahjo_details_request( application_with_ahjo_case_id, application_status, diff --git a/backend/benefit/applications/tests/test_ahjo_payload.py b/backend/benefit/applications/tests/test_ahjo_payload.py index 1edb9f9506..bc99c4d536 100644 --- a/backend/benefit/applications/tests/test_ahjo_payload.py +++ b/backend/benefit/applications/tests/test_ahjo_payload.py @@ -106,6 +106,7 @@ def test_ahjo_base_record_title_str(): assert result == expected +@pytest.mark.django_db def test_prepare_case_title(decided_application): application = decided_application wanted_title = ( @@ -146,6 +147,7 @@ def test_truncate_string_to_limit( ("a" * 256, 512, 512), ], ) +@pytest.mark.django_db def test_prepare_final_case_title_truncate( decided_application, company_name, limit, expected_length ): @@ -197,6 +199,7 @@ def test_prepare_final_case_title_truncate( ), ], ) +@pytest.mark.django_db def test_prepare_record_title( title_class, decided_application, @@ -233,6 +236,7 @@ def test_prepare_record_title( assert wanted_title == got +@pytest.mark.django_db def test_prepare_record_title_for_attachment(decided_application): application = Application.objects.get(pk=decided_application.pk) @@ -245,6 +249,7 @@ def test_prepare_record_title_for_attachment(decided_application): assert wanted_title == got +@pytest.mark.django_db def test_prepare_application_record( decided_application, ahjo_payload_record_for_application ): @@ -261,6 +266,7 @@ def test_prepare_application_record( assert ahjo_payload_record_for_application == record +@pytest.mark.django_db def test_prepare_attachment_record( decided_application, ahjo_payload_record_for_attachment ): @@ -277,6 +283,7 @@ def test_prepare_attachment_record( assert ahjo_payload_record_for_attachment == record +@pytest.mark.django_db def test_prepare_attachment_update_record( decided_application, ahjo_payload_record_for_attachment_update, @@ -296,6 +303,7 @@ def test_prepare_attachment_update_record( assert ahjo_payload_record_for_attachment_update == record +@pytest.mark.django_db def test_prepare_record_document_dict(decided_application, settings): settings.DEBUG = True settings.API_BASE_URL = "http://test.com" @@ -316,6 +324,7 @@ def test_prepare_record_document_dict(decided_application, settings): assert want == got +@pytest.mark.django_db def test_prepare_case_records(decided_application, settings): settings.DEBUG = True application = Application.objects.get(pk=decided_application.pk) @@ -337,7 +346,7 @@ def test_prepare_case_records(decided_application, settings): { "Title": f"{OpenCaseRecordTitle(application=application)}", "Type": AhjoRecordType.APPLICATION, - "Acquired": application.created_at.isoformat("T", "seconds"), + "Acquired": fake_summary.created_at.isoformat("T", "seconds"), "PublicityClass": "Salassa pidettävä", "SecurityReasons": ["JulkL (621/1999) 24.1 § 25 k"], "Language": "fi", @@ -380,6 +389,7 @@ def test_prepare_case_records(decided_application, settings): assert want == got +@pytest.mark.django_db def test_prepare_top_level_dict(decided_application, ahjo_open_case_top_level_dict): application = Application.objects.get(pk=decided_application.pk) long_title = "a" * 512 @@ -392,6 +402,7 @@ def test_prepare_top_level_dict(decided_application, ahjo_open_case_top_level_di assert ahjo_open_case_top_level_dict == got +@pytest.mark.django_db def test_prepare_update_application_payload(decided_application): application = Application.objects.get(pk=decided_application.pk) @@ -419,7 +430,7 @@ def test_prepare_update_application_payload(decided_application): { "Title": f"{title}", "Type": AhjoRecordType.APPLICATION, - "Acquired": application.submitted_at.isoformat(), + "Acquired": fake_summary.created_at.isoformat("T", "seconds"), "PublicityClass": "Salassa pidettävä", "SecurityReasons": ["JulkL (621/1999) 24.1 § 25 k"], "Language": "fi", @@ -453,6 +464,7 @@ def test_prepare_update_application_payload(decided_application): ("en", "fi"), ], ) +@pytest.mark.django_db def test_resolve_payload_language( decided_application, applicant_language, expected_payload_language ): @@ -463,6 +475,7 @@ def test_resolve_payload_language( assert expected_payload_language == got +@pytest.mark.django_db def test_prepare_decision_proposal_payload(application_with_ahjo_decision): application = application_with_ahjo_decision handler = application.calculation.handler diff --git a/backend/benefit/applications/tests/test_ahjo_requests.py b/backend/benefit/applications/tests/test_ahjo_requests.py index 60299c18fd..b8f8ccf04d 100644 --- a/backend/benefit/applications/tests/test_ahjo_requests.py +++ b/backend/benefit/applications/tests/test_ahjo_requests.py @@ -61,6 +61,7 @@ def ahjo_open_case_request(application_with_ahjo_case_id): ), ], ) +@pytest.mark.django_db def test_ahjo_requests_without_application( ahjo_request_class, request_type, @@ -153,6 +154,7 @@ def test_ahjo_requests_without_application( ), ], ) +@pytest.mark.django_db def test_ahjo_application_requests( ahjo_request_class, application_with_ahjo_case_id, @@ -296,6 +298,7 @@ def test_ahjo_application_requests( ), ], ) +@pytest.mark.django_db @patch("applications.services.ahjo_client.LOGGER") def test_requests_exceptions( mock_logger, diff --git a/backend/benefit/applications/tests/test_ahjo_response_handler.py b/backend/benefit/applications/tests/test_ahjo_response_handler.py index 6c3f3e539c..c326b36959 100644 --- a/backend/benefit/applications/tests/test_ahjo_response_handler.py +++ b/backend/benefit/applications/tests/test_ahjo_response_handler.py @@ -180,6 +180,7 @@ def test_save_ahjo_settings_database_error(setting_name, test_data): AhjoResponseHandler._save_ahjo_setting(setting_name, test_data) +@pytest.mark.django_db def test_parse_details_from_decision_response( ahjo_decision_detail_response, application_with_ahjo_decision ): @@ -227,6 +228,7 @@ def test_parse_details_from_decision_response( ), ], ) +@pytest.mark.django_db def test_handle_details_request_success( ahjo_decision_detail_response, decided_application_with_decision_date, diff --git a/backend/benefit/applications/tests/test_ahjo_settings.py b/backend/benefit/applications/tests/test_ahjo_settings.py index c5a5471800..05ff3c99da 100644 --- a/backend/benefit/applications/tests/test_ahjo_settings.py +++ b/backend/benefit/applications/tests/test_ahjo_settings.py @@ -5,6 +5,7 @@ # settings_route = reverse("ahjo-setting-detail", args=[name_value]) +@pytest.mark.django_db def test_get_decision_maker_ahjo_setting_for_applicant(api_client): response = api_client.get( reverse("ahjo-setting-detail", args=["ahjo_decision_maker"]) diff --git a/backend/benefit/applications/tests/test_ahjo_xml_builder.py b/backend/benefit/applications/tests/test_ahjo_xml_builder.py index 11aa36277f..1bf48570da 100644 --- a/backend/benefit/applications/tests/test_ahjo_xml_builder.py +++ b/backend/benefit/applications/tests/test_ahjo_xml_builder.py @@ -105,6 +105,7 @@ def test_generate_public_xml_string(accepted_ahjo_decision_text, public_xml_buil assert wanted_decision_text in xml_content +@pytest.mark.django_db def test_public_xml_file_name(decided_application, public_xml_builder): application = decided_application xml_file_name = public_xml_builder.generate_xml_file_name() @@ -124,6 +125,7 @@ def test_public_xml_file_name(decided_application, public_xml_builder): ApplicationStatus.REJECTED, ], ) +@pytest.mark.django_db def test_secret_xml_decision_string( application_status, decided_application, secret_xml_builder ): @@ -181,6 +183,7 @@ def test_secret_xml_decision_string( assert all([replacement in xml_string for replacement in wanted_replacements]) +@pytest.mark.django_db def test_secret_xml_file_name(decided_application, secret_xml_builder): application = decided_application xml_file_name = secret_xml_builder.generate_xml_file_name() @@ -211,6 +214,7 @@ def test_get_period_rows_for_xml( assert monthly_row_1 in calculation_rows +@pytest.mark.django_db def test_prepare_multiple_period_rows( secret_xml_builder, monthly_row_1, sub_total_row_1, monthly_row_2, sub_total_row_2 ): @@ -230,6 +234,7 @@ def test_prepare_multiple_period_rows( assert period_rows[1].total_amount == int(sub_total_row_2.amount) +@pytest.mark.django_db def test_prepare_single_period_row(secret_xml_builder, monthly_row_1, total_eur_row): period_rows = secret_xml_builder._prepare_single_period_row( monthly_row_1, total_eur_row @@ -242,6 +247,7 @@ def test_prepare_single_period_row(secret_xml_builder, monthly_row_1, total_eur_ assert period_rows[0].total_amount == int(total_eur_row.amount) +@pytest.mark.django_db def test_get_context_for_secret_xml_for_rejected_application( decided_application, secret_xml_builder ): @@ -257,6 +263,7 @@ def test_get_context_for_secret_xml_for_rejected_application( assert "total_amount_row" not in context +@pytest.mark.django_db def test_get_context_for_secret_xml_with_single_period( decided_application, monthly_row_1, total_eur_row, secret_xml_builder ): @@ -278,6 +285,7 @@ def test_get_context_for_secret_xml_with_single_period( assert context["total_amount_row"].amount == int(total_eur_row.amount) +@pytest.mark.django_db def test_get_context_for_secret_xml_with_multiple_periods( calculation, decided_application, diff --git a/backend/benefit/applications/tests/test_alteration_api.py b/backend/benefit/applications/tests/test_alteration_api.py index c509c30d37..757491c080 100644 --- a/backend/benefit/applications/tests/test_alteration_api.py +++ b/backend/benefit/applications/tests/test_alteration_api.py @@ -10,6 +10,7 @@ from companies.tests.factories import CompanyFactory +@pytest.mark.django_db def test_application_alteration_create_terminated(api_client, application): pk = application.id @@ -27,6 +28,7 @@ def test_application_alteration_create_terminated(api_client, application): assert response.status_code == 201 +@pytest.mark.django_db def test_application_alteration_create_suspended(api_client, application): pk = application.id @@ -45,6 +47,7 @@ def test_application_alteration_create_suspended(api_client, application): assert response.status_code == 201 +@pytest.mark.django_db def test_application_alteration_create_missing_resume_date(api_client, application): pk = application.id @@ -64,6 +67,7 @@ def test_application_alteration_create_missing_resume_date(api_client, applicati assert len(response.data["non_field_errors"]) == 1 +@pytest.mark.django_db def test_application_alteration_create_missing_einvoice_fields(api_client, application): pk = application.id @@ -83,6 +87,7 @@ def test_application_alteration_create_missing_einvoice_fields(api_client, appli assert len(response.data["non_field_errors"]) == 3 +@pytest.mark.django_db def test_application_alteration_create_missing_contact_person_name( api_client, application ): @@ -104,6 +109,7 @@ def test_application_alteration_create_missing_contact_person_name( assert len(response.data.keys()) == 1 +@pytest.mark.django_db def test_application_alteration_create_use_einvoice(api_client, application): pk = application.id @@ -124,6 +130,7 @@ def test_application_alteration_create_use_einvoice(api_client, application): assert response.status_code == 201 +@pytest.mark.django_db def test_application_alteration_create_outside_application_date_range( api_client, application ): @@ -173,6 +180,7 @@ def test_application_alteration_create_outside_application_date_range( assert "non_field_errors" in response.data +@pytest.mark.django_db def test_application_alteration_create_reversed_suspension_dates( api_client, application ): @@ -195,6 +203,7 @@ def test_application_alteration_create_reversed_suspension_dates( assert len(response.data["non_field_errors"]) == 1 +@pytest.mark.django_db def test_application_alteration_create_overlapping_alteration(api_client, application): pk = application.id @@ -268,6 +277,7 @@ def test_application_alteration_create_overlapping_alteration(api_client, applic assert len(response.data["non_field_errors"]) == 1 +@pytest.mark.django_db def test_application_alteration_create_non_overlapping_alteration( api_client, application ): @@ -309,6 +319,7 @@ def test_application_alteration_create_non_overlapping_alteration( assert response.status_code == 201 +@pytest.mark.django_db def test_application_alteration_create_forbidden_anonymous( anonymous_client, application ): @@ -328,6 +339,7 @@ def test_application_alteration_create_forbidden_anonymous( assert response.status_code == 403 +@pytest.mark.django_db def test_application_alteration_create_forbidden_another_company( api_client, application ): @@ -351,6 +363,7 @@ def test_application_alteration_create_forbidden_another_company( assert response.status_code == 403 +@pytest.mark.django_db def test_application_alteration_forbidden_applicant_in_handler_api( api_client, application ): @@ -389,6 +402,7 @@ def test_application_alteration_forbidden_handler_in_applicant_api( assert response.status_code == 403 +@pytest.mark.django_db def test_application_alteration_create_ignored_fields_applicant( api_client, application, @@ -454,6 +468,7 @@ def test_application_alteration_create_ignored_fields_handler( assert response.data["recovery_amount"] == "4000.00" +@pytest.mark.django_db def test_application_alteration_patch_applicant(api_client, application): pk = application.id response = api_client.post( @@ -594,6 +609,7 @@ def test_application_alteration_patch_allowed_edit_states_handler( (ApplicationAlterationState.CANCELLED, 403), ], ) +@pytest.mark.django_db def test_application_alteration_patch_allowed_edit_states_applicant( api_client, application, initial_state, result ): @@ -610,6 +626,7 @@ def test_application_alteration_patch_allowed_edit_states_applicant( assert response.status_code == result +@pytest.mark.django_db def test_application_alteration_patch_forbidden_another_company( api_client, application ): @@ -637,6 +654,7 @@ def test_application_alteration_patch_forbidden_another_company( (ApplicationAlterationState.CANCELLED, 403), ], ) +@pytest.mark.django_db def test_application_alteration_allowed_delete_states_applicant( api_client, application, initial_state, result ): @@ -674,6 +692,7 @@ def test_application_alteration_delete_handler( assert response.status_code == result +@pytest.mark.django_db def test_application_alteration_delete_forbidden_another_company( api_client, application ): @@ -689,6 +708,7 @@ def test_application_alteration_delete_forbidden_another_company( assert response.status_code == 403 +@pytest.mark.django_db def test_application_alteration_patch_forbidden_applicant_get_requests( api_client, application ): diff --git a/backend/benefit/applications/tests/test_application_batch_api.py b/backend/benefit/applications/tests/test_application_batch_api.py index d34de55267..27c34a6394 100755 --- a/backend/benefit/applications/tests/test_application_batch_api.py +++ b/backend/benefit/applications/tests/test_application_batch_api.py @@ -5,9 +5,9 @@ from unittest.mock import patch import pytest -import pytz from dateutil.relativedelta import relativedelta from django.http import HttpResponse +from django.utils import timezone from rest_framework.reverse import reverse from applications.api.v1.serializers.application import ApplicationBatchSerializer @@ -24,14 +24,6 @@ from applications.tests.test_applications_api import get_handler_detail_url -@pytest.fixture(autouse=True) -def run_before_and_after_tests(): - from applications.tests.before_after import before_test_reseed - - before_test_reseed([]) - yield - - def get_valid_batch_completion_data(): return { "decision_maker_title": get_faker().job(), @@ -96,16 +88,19 @@ def get_batch_detail_url(application_batch, uri=""): ) +@pytest.mark.django_db def test_get_application_batch_unauthenticated(anonymous_client, application_batch): response = anonymous_client.get(get_batch_detail_url(application_batch)) assert response.status_code == 403 +@pytest.mark.django_db def test_get_application_batch_as_applicant(api_client, application_batch): response = api_client.get(get_batch_detail_url(application_batch)) assert response.status_code == 403 +@pytest.mark.django_db def test_get_application_batch(handler_api_client, application_batch): response = handler_api_client.get(get_batch_detail_url(application_batch)) assert response.status_code == 200 @@ -313,6 +308,7 @@ def test_batch_status_change( assert response.data["status"] == changed_status +@pytest.mark.django_db def test_batch_too_many_drafts(application_batch): # Create a second batch to get to two batch limit ( @@ -471,6 +467,7 @@ def test_get_application_with_ahjo_decision( assert response.data["batch"]["status"] == batch_status +@pytest.mark.freeze_time def test_application_post_success(handler_api_client, application_batch): """ Create a new application batch @@ -497,9 +494,7 @@ def test_application_post_success(handler_api_client, application_batch): new_application_batch = ApplicationBatch.objects.all().first() assert new_application_batch.decision_maker_title == data["decision_maker_title"] - assert datetime.fromisoformat(data["created_at"]) == datetime( - 2021, 6, 4, tzinfo=pytz.UTC - ) + assert datetime.fromisoformat(data["created_at"]) == timezone.now() def test_application_post_success_with_applications( @@ -821,6 +816,7 @@ def test_application_batch_export(mock_export, handler_api_client, application_b assert response.status_code == 400 +@pytest.mark.django_db def test_application_batches_talpa_export( anonymous_client, application_batch, settings ): @@ -857,6 +853,7 @@ def test_application_batches_talpa_export( assert response.status_code == 200 +@pytest.mark.django_db def test_application_instalments_talpa_export(anonymous_client, settings): settings.PAYMENT_INSTALMENTS_ENABLED = True url = reverse("v1:applicationbatch-talpa-export-batch") diff --git a/backend/benefit/applications/tests/test_application_change_sets.py b/backend/benefit/applications/tests/test_application_change_sets.py index 2ea174aa7c..a002f251ba 100755 --- a/backend/benefit/applications/tests/test_application_change_sets.py +++ b/backend/benefit/applications/tests/test_application_change_sets.py @@ -2,6 +2,7 @@ from unittest import mock import faker +import pytest from freezegun import freeze_time from rest_framework.reverse import reverse @@ -185,6 +186,9 @@ def check_applicant_changes(applicant_edit_payloads, changes, application): assert len(changes) == len(applicant_edit_payloads) +@pytest.mark.django_db +# Freeze so de minimis has reasonable granted_at +@pytest.mark.freeze_time("2023-01-01") def test_application_history_change_sets( request, handler_api_client, api_client, application ): @@ -198,7 +202,6 @@ def test_application_history_change_sets( get_handler_detail_url(application), payload, ) - assert response.status_code == 200 application.refresh_from_db() diff --git a/backend/benefit/applications/tests/test_application_clone.py b/backend/benefit/applications/tests/test_application_clone.py index 4945c18da4..92e3d97c80 100755 --- a/backend/benefit/applications/tests/test_application_clone.py +++ b/backend/benefit/applications/tests/test_application_clone.py @@ -1,6 +1,7 @@ import uuid from datetime import date, datetime +import pytest from freezegun import freeze_time from freezegun.api import FakeDate from rest_framework.reverse import reverse @@ -194,6 +195,7 @@ } +@pytest.mark.django_db def test_application_full_clone(api_client, handler_api_client, settings): settings.PAYMENT_INSTALMENTS_ENABLED = True application = _set_up_decided_application() diff --git a/backend/benefit/applications/tests/test_application_search.py b/backend/benefit/applications/tests/test_application_search.py index 4eff023442..209221273a 100755 --- a/backend/benefit/applications/tests/test_application_search.py +++ b/backend/benefit/applications/tests/test_application_search.py @@ -4,6 +4,7 @@ import pytest from dateutil.relativedelta import relativedelta from django.core.management import call_command +from django.utils import timezone from rest_framework.reverse import reverse from applications.api.v1.search_views import SearchPattern, SubsidyInEffect @@ -252,7 +253,7 @@ def test_search_filter_years_since_decision( handler_api_client, q, detected_pattern, archived, application, years_since_decision ): application = setup_application_data(application, archived) - application.batch.decision_date = datetime.now() - relativedelta(years=3) + application.batch.decision_date = timezone.now() - relativedelta(years=3) application.batch.save() params = urlencode( @@ -263,8 +264,8 @@ def test_search_filter_years_since_decision( "archival": True, } ) - response = handler_api_client.get(f"{api_url}?{params}") + data = response.json() assert response.status_code == 200 assert len(data["matches"]) > 0 @@ -272,7 +273,7 @@ def test_search_filter_years_since_decision( assert data["matches"][0]["application_number"] == application.application_number # Decision date is one day over three years - application.batch.decision_date = datetime.now() - relativedelta(years=3, days=1) + application.batch.decision_date = timezone.now() - relativedelta(years=3, days=1) application.batch.save() response = handler_api_client.get(f"{api_url}?{params}") data = response.json() diff --git a/backend/benefit/applications/tests/test_application_tasks.py b/backend/benefit/applications/tests/test_application_tasks.py index 6d7a97d714..20dd02f5bb 100755 --- a/backend/benefit/applications/tests/test_application_tasks.py +++ b/backend/benefit/applications/tests/test_application_tasks.py @@ -25,6 +25,7 @@ from applications.tests.factories import CancelledApplicationFactory +@pytest.mark.django_db def test_seed_applications_with_arguments(set_debug_to_true): amount = 4 statuses = ApplicationStatus.values @@ -52,6 +53,7 @@ def test_seed_is_not_allowed_when_debug_is_false(set_debug_to_false): ) +@pytest.mark.django_db def test_delete_cancelled_applications_older_than_30_days(cancelled_to_delete): out = StringIO() status = ApplicationStatus.CANCELLED @@ -97,6 +99,7 @@ def test_delete_cancelled_applications_older_than_30_days(cancelled_to_delete): ) +@pytest.mark.django_db def test_delete_draft_applications_older_than_180_days( drafts_to_delete, drafts_to_keep ): @@ -142,6 +145,7 @@ def test_delete_draft_applications_older_than_180_days( ) +@pytest.mark.django_db def test_user_is_notified_of_upcoming_application_deletion(drafts_about_to_be_deleted): out = StringIO() call_command( diff --git a/backend/benefit/applications/tests/test_applications_api.py b/backend/benefit/applications/tests/test_applications_api.py index 01f5ce9ede..3a45c84e1b 100755 --- a/backend/benefit/applications/tests/test_applications_api.py +++ b/backend/benefit/applications/tests/test_applications_api.py @@ -4,7 +4,8 @@ import re import tempfile import uuid -from datetime import date, datetime, timezone +from datetime import date, datetime +from datetime import timezone as tz from decimal import Decimal from unittest import mock @@ -49,13 +50,10 @@ from applications.tests.test_alteration_api import _create_application_alteration from calculator.models import Calculation from calculator.tests.conftest import fill_empty_calculation_fields -from common.tests.conftest import * # noqa from common.tests.conftest import get_client_user from common.utils import duration_in_months -from companies.tests.conftest import * # noqa from companies.tests.factories import CompanyFactory from helsinkibenefit.settings import MAX_UPLOAD_SIZE -from helsinkibenefit.tests.conftest import * # noqa from messages.automatic_messages import ( get_additional_information_email_notification_subject, ) @@ -64,15 +62,6 @@ from shared.audit_log import models as audit_models from shared.service_bus.enums import YtjOrganizationCode from terms.models import TermsOfServiceApproval -from terms.tests.conftest import * # noqa - - -@pytest.fixture(autouse=True) -def run_before_and_after_tests(): - from applications.tests.before_after import before_test_reseed - - before_test_reseed(["test_application_history_change_sets_for_handler"]) - yield def get_detail_url(application): @@ -92,6 +81,7 @@ def get_handler_detail_url(application): "v1:handler-application-simplified-application-list", ], ) +@pytest.mark.django_db def test_applications_unauthenticated(anonymous_client, application, view_name): response = anonymous_client.get(reverse(view_name)) assert response.status_code == 403 @@ -111,6 +101,7 @@ def test_applications_unauthenticated(anonymous_client, application, view_name): "v1:applicant-application-simplified-application-list", ], ) +@pytest.mark.django_db def test_applications_unauthorized( api_client, anonymous_application, @@ -122,12 +113,14 @@ def test_applications_unauthorized( assert response.status_code == 200 +@pytest.mark.django_db def test_applications_list(api_client, application): response = api_client.get(reverse("v1:applicant-application-list")) assert len(response.data) == 1 assert response.status_code == 200 +@pytest.mark.django_db def test_applications_list_with_filter(api_client, application): application.status = ApplicationStatus.DRAFT application.save() @@ -147,6 +140,7 @@ def test_applications_list_with_filter(api_client, application): assert response.status_code == 200 +@pytest.mark.django_db def test_applications_filter_by_batch( handler_api_client, application_batch, application ): @@ -157,6 +151,7 @@ def test_applications_filter_by_batch( assert response.status_code == 200 +@pytest.mark.django_db def test_applications_filter_by_ssn(api_client, application, association_application): assert ( application.employee.social_security_number @@ -172,6 +167,7 @@ def test_applications_filter_by_ssn(api_client, application, association_applica assert response.status_code == 200 +@pytest.mark.django_db def test_applications_filter_by_employee_first_name(api_client, application): url = ( reverse("v1:applicant-application-list") @@ -183,6 +179,7 @@ def test_applications_filter_by_employee_first_name(api_client, application): assert response.status_code == 200 +@pytest.mark.django_db def test_applications_filter_by_employee_last_name(api_client, application): url = ( reverse("v1:applicant-application-list") @@ -194,6 +191,7 @@ def test_applications_filter_by_employee_last_name(api_client, application): assert response.status_code == 200 +@pytest.mark.django_db def test_applications_filter_by_application_number( handler_api_client, received_application ): @@ -210,6 +208,7 @@ def test_applications_filter_by_application_number( assert response.status_code == 200 +@pytest.mark.django_db def test_applications_simple_list_as_handler(handler_api_client, received_application): response = handler_api_client.get( reverse("v1:handler-application-simplified-application-list") @@ -223,6 +222,7 @@ def test_applications_simple_list_as_handler(handler_api_client, received_applic assert key in response.data[0] +@pytest.mark.django_db def test_applications_simple_list_as_applicant(api_client, received_application): response = api_client.get( reverse("v1:applicant-application-simplified-application-list") @@ -247,6 +247,7 @@ def test_applications_simple_list_as_applicant(api_client, received_application) ("status", "last_modified_at", "employee"), ], ) +@pytest.mark.django_db def test_applications_simple_list_exclude_more( handler_api_client, received_application, exclude_fields ): @@ -257,6 +258,7 @@ def test_applications_simple_list_exclude_more( assert response.status_code == 200 +@pytest.mark.django_db def test_applications_simple_list_filter( handler_api_client, received_application, handling_application ): @@ -271,6 +273,7 @@ def test_applications_simple_list_filter( assert response.status_code == 200 +@pytest.mark.django_db def test_applications_filter_archived_for_applicant( api_client, mock_get_organisation_roles_and_create_company ): @@ -334,6 +337,7 @@ def test_applications_filter_archived_for_applicant( @pytest.mark.parametrize("url_func", [get_detail_url, get_handler_detail_url]) +@pytest.mark.django_db def test_application_single_read_unauthenticated( anonymous_client, application, url_func ): @@ -341,6 +345,7 @@ def test_application_single_read_unauthenticated( assert response.status_code == 403 +@pytest.mark.django_db def test_application_single_read_unauthorized( api_client, anonymous_application, mock_get_organisation_roles_and_create_company ): @@ -363,6 +368,7 @@ def test_application_single_read_unauthorized( (ApplicationStatus.CANCELLED, ApplicationStatus.CANCELLED), ], ) +@pytest.mark.django_db def test_application_single_read_without_ahjo_decision_as_applicant( api_client, application, actual_status, visible_status ): @@ -397,6 +403,7 @@ def test_application_single_read_without_ahjo_decision_as_applicant( (ApplicationStatus.REJECTED, ApplicationStatus.REJECTED), ], ) +@pytest.mark.django_db def test_application_single_read_with_ahjo_decision_as_applicant( api_client, application, actual_status, visible_status ): @@ -430,6 +437,7 @@ def test_application_single_read_with_ahjo_decision_as_applicant( (ApplicationStatus.CANCELLED, 200), ], ) +@pytest.mark.django_db def test_application_single_read_as_handler( handler_api_client, application, status, expected_result ): @@ -443,6 +451,8 @@ def test_application_single_read_as_handler( assert "batch" in response.data +@pytest.mark.django_db +@pytest.mark.freeze_time("2021-06-04") def test_application_submitted_at( api_client, application, received_application, handling_application ): @@ -454,7 +464,10 @@ def test_application_submitted_at( assert response.data["submitted_at"].isoformat() == "2021-06-04T00:00:00+00:00" -def test_application_template(api_client): +@pytest.mark.django_db +def test_application_template( + api_client, mock_get_organisation_roles_and_create_company +): response = api_client.get( reverse("v1:applicant-application-get-application-template") ) @@ -463,6 +476,7 @@ def test_application_template(api_client): ) # as of 2021-06-16, just a dummy implementation exists +@pytest.mark.django_db def test_application_post_success_unauthenticated(anonymous_client, application): data = ApplicantApplicationSerializer(application).data application.delete() @@ -483,6 +497,8 @@ def test_application_post_success_unauthenticated(anonymous_client, application) assert audit_event["target"]["type"] == "Application" +@pytest.mark.django_db +@pytest.mark.freeze_time("2021-06-04") def test_application_post_success(api_client, application): """ Create a new application @@ -507,7 +523,7 @@ def test_application_post_success(api_client, application): == data["company_contact_person_phone_number"] ) assert datetime.fromisoformat(data["created_at"]) == datetime( - 2021, 6, 4, tzinfo=timezone.utc + 2021, 6, 4, tzinfo=tz.utc ) assert new_application.application_step == data["application_step"] assert {v.identifier for v in new_application.bases.all()} == { @@ -536,6 +552,7 @@ def test_application_post_success(api_client, application): assert audit_event["operation"] == "CREATE" +@pytest.mark.django_db def test_application_post_unfinished(api_client, application): """ Create a new application with partial information @@ -597,6 +614,7 @@ def test_application_post_unfinished(api_client, application): ("sv", "Felaktigt IBAN-kontonummer"), ], ) +@pytest.mark.django_db def test_application_post_invalid_data( api_client, application, language, company_bank_account_number_validation_error ): @@ -637,6 +655,7 @@ def test_application_post_invalid_data( assert len(response.data["de_minimis_aid_set"]) == 2 +@pytest.mark.django_db def test_application_post_invalid_employee_data(api_client, application): data = ApplicantApplicationSerializer(application).data application.delete() @@ -674,6 +693,7 @@ def test_application_post_invalid_employee_data(api_client, application): ) # Check if the error still there +@pytest.mark.django_db def test_application_put_edit_fields_unauthenticated(anonymous_client, application): data = ApplicantApplicationSerializer(application).data data["company_contact_person_phone_number"] = "+358505658789" @@ -684,6 +704,7 @@ def test_application_put_edit_fields_unauthenticated(anonymous_client, applicati assert response.status_code == 403 +@pytest.mark.django_db def test_application_put_edit_fields_unauthorized( api_client, anonymous_application, mock_get_organisation_roles_and_create_company ): @@ -702,6 +723,7 @@ def test_application_put_edit_fields_unauthorized( assert audit_event["operation"] == "UPDATE" +@pytest.mark.django_db def test_application_put_edit_fields(api_client, application): """ modify existing application @@ -726,6 +748,7 @@ def test_application_put_edit_fields(api_client, application): assert audit_event["operation"] == "UPDATE" +@pytest.mark.django_db def test_application_put_edit_employee(api_client, application): """ modify existing application @@ -744,6 +767,7 @@ def test_application_put_edit_employee(api_client, application): assert old_employee_pk == application.employee.pk +@pytest.mark.django_db def test_application_put_read_only_fields(api_client, application): """ company info that is retrieved from official (YTJ or other) sources is not editable by applicant/handler @@ -775,6 +799,7 @@ def test_application_put_read_only_fields(api_client, application): ) +@pytest.mark.django_db def test_application_put_invalid_data(api_client, application): data = ApplicantApplicationSerializer(application).data data["de_minimis_aid_set"][0]["amount"] = "300001.00" # value too high @@ -798,6 +823,7 @@ def test_application_put_invalid_data(api_client, application): assert len(response.data["de_minimis_aid_set"]) == 2 +@pytest.mark.django_db def test_application_replace_de_minimis_aid(api_client, application): data = ApplicantApplicationSerializer(application).data @@ -822,6 +848,7 @@ def test_application_replace_de_minimis_aid(api_client, application): assert new_data["de_minimis_aid_set"] == data["de_minimis_aid_set"] +@pytest.mark.django_db def test_application_edit_de_minimis_aid(api_client, application): data = ApplicantApplicationSerializer(application).data @@ -842,6 +869,7 @@ def test_application_edit_de_minimis_aid(api_client, application): assert response.data["de_minimis_aid_set"][1]["ordering"] == 1 +@pytest.mark.django_db def test_application_delete_de_minimis_aid(api_client, application): data = ApplicantApplicationSerializer(application).data @@ -856,6 +884,7 @@ def test_application_delete_de_minimis_aid(api_client, application): assert response.data["de_minimis_aid_set"] == [] +@pytest.mark.django_db def test_application_edit_de_minimis_aid_too_high(api_client, application): data = ApplicantApplicationSerializer(application).data @@ -875,6 +904,7 @@ def test_application_edit_de_minimis_aid_too_high(api_client, application): assert previous_aid == data_after["de_minimis_aid_set"] +@pytest.mark.django_db def test_application_edit_benefit_type_business(api_client, application): data = ApplicantApplicationSerializer(application).data data["benefit_type"] = BenefitType.EMPLOYMENT_BENEFIT @@ -893,6 +923,7 @@ def test_application_edit_benefit_type_business(api_client, application): } +@pytest.mark.django_db def test_application_edit_benefit_type_business_no_pay_subsidy(api_client, application): data = ApplicantApplicationSerializer(application).data data["benefit_type"] = BenefitType.EMPLOYMENT_BENEFIT @@ -911,6 +942,7 @@ def test_application_edit_benefit_type_business_no_pay_subsidy(api_client, appli } +@pytest.mark.django_db def test_application_edit_benefit_type_business_association( api_client, association_application, mock_get_organisation_roles_and_create_company ): @@ -939,6 +971,7 @@ def test_application_edit_benefit_type_business_association( } +@pytest.mark.django_db def test_application_edit_benefit_type_business_association_with_apprenticeship( api_client, association_application ): @@ -960,6 +993,7 @@ def test_application_edit_benefit_type_business_association_with_apprenticeship( } +@pytest.mark.django_db def test_application_edit_benefit_type_non_business( api_client, association_application ): @@ -977,6 +1011,7 @@ def test_application_edit_benefit_type_non_business( ] +@pytest.mark.django_db def test_application_edit_benefit_type_non_business_invalid( api_client, association_application ): @@ -993,6 +1028,7 @@ def test_application_edit_benefit_type_non_business_invalid( assert response.status_code == 400 +@pytest.mark.django_db def test_association_immediate_manager_check_valid(api_client, association_application): data = ApplicantApplicationSerializer(association_application).data data["association_immediate_manager_check"] = True # valid value for associations @@ -1043,6 +1079,7 @@ def test_application_delete(api_client, application): ), # past year is allowed, as the application is not submitted ], ) +@pytest.mark.django_db def test_application_date_range( api_client, application, benefit_type, start_date, end_date, status_code ): @@ -1087,6 +1124,8 @@ def test_application_date_range( ), # start_date in next year (relative to freeze_time date) ], ) +@pytest.mark.django_db +@pytest.mark.freeze_time("2023-01-01") def test_application_date_range_on_submit( request, api_client, application, start_date, end_date, status_code ): @@ -1111,6 +1150,7 @@ def test_application_date_range_on_submit( assert submit_response.status_code == status_code +@pytest.mark.django_db def test_application_has_default_ahjo_status_after_submit( request, api_client, application ): @@ -1124,6 +1164,7 @@ def test_application_has_default_ahjo_status_after_submit( ) application.refresh_from_db() submit_response = _submit_application(api_client, application) + assert submit_response.status_code == 200 assert ( submit_response.data["ahjo_status"] == AhjoStatus.SUBMITTED_BUT_NOT_SENT_TO_AHJO ) @@ -1140,6 +1181,7 @@ def test_application_has_default_ahjo_status_after_submit( (YtjOrganizationCode.COMPANY_FORM_CODE_DEFAULT, False, [], None, 200), ], ) +@pytest.mark.django_db def test_submit_application_without_de_minimis_aid( request, api_client, @@ -1192,6 +1234,7 @@ def test_submit_application_without_de_minimis_aid( (PaySubsidyGranted.NOT_GRANTED, True, 200, 400), ], ) +@pytest.mark.django_db def test_apprenticeship_program_validation_on_submit( request, api_client, @@ -1250,6 +1293,8 @@ def test_apprenticeship_program_validation_on_submit( (ApplicationStatus.REJECTED, ApplicationStatus.DRAFT, 400), ], ) +@pytest.mark.django_db +@pytest.mark.freeze_time def test_application_status_change_as_applicant( request, api_client, application, from_status, to_status, expected_code ): @@ -1286,7 +1331,7 @@ def test_application_status_change_as_applicant( assert application.log_entries.all().first().to_status == to_status if to_status == ApplicationStatus.RECEIVED: assert response.data["submitted_at"] == datetime.now().replace( - tzinfo=timezone.utc + tzinfo=tz.utc ) else: assert response.data["submitted_at"] is None @@ -1324,6 +1369,8 @@ def test_application_status_change_as_applicant( ], ) @pytest.mark.parametrize("log_entry_comment", [None, "", "comment"]) +@pytest.mark.django_db +@pytest.mark.freeze_time @override_settings(EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend") def test_application_status_change_as_handler( request, @@ -1397,9 +1444,7 @@ def test_application_status_change_as_handler( assert ( response.data["latest_decision_comment"] == expected_log_entry_comment ) - assert response.data["handled_at"] == datetime.now().replace( - tzinfo=timezone.utc - ) + assert response.data["handled_at"] == datetime.now().replace(tzinfo=tz.utc) else: assert response.data["latest_decision_comment"] is None assert response.data["handled_at"] is None @@ -1407,6 +1452,7 @@ def test_application_status_change_as_handler( assert application.log_entries.all().count() == 0 +@pytest.mark.django_db def test_application_accept( request, handler_api_client, @@ -1449,6 +1495,7 @@ def test_application_accept( ("KISSA123", True, True), ], ) +@pytest.mark.django_db def test_application_with_batch_back_to_handling( request, handler_api_client, @@ -1499,6 +1546,7 @@ def test_application_with_batch_back_to_handling( (ApplicationStatus.HANDLING, ApplicationStatus.CANCELLED, True), ], ) +@pytest.mark.django_db def test_application_status_change_as_handler_auto_assign_handler( request, handler_api_client, application, from_status, to_status, auto_assign ): @@ -1563,6 +1611,8 @@ def add_attachments_to_application(request, application, is_handler_application= _add_pdf_attachment(request, application, AttachmentType.FULL_APPLICATION) +@pytest.mark.django_db +@pytest.mark.freeze_time("2021-06-04") def test_application_modified_at_draft(api_client, application): """ DRAFT application's last_modified_at is visible to applicant @@ -1570,7 +1620,7 @@ def test_application_modified_at_draft(api_client, application): application.status = ApplicationStatus.DRAFT application.save() data = ApplicantApplicationSerializer(application).data - assert data["modified_at"] == datetime(2021, 6, 4, tzinfo=timezone.utc) + assert data["modified_at"] == datetime(2021, 6, 4, tzinfo=tz.utc) @pytest.mark.parametrize( @@ -1583,6 +1633,7 @@ def test_application_modified_at_draft(api_client, application): ApplicationStatus.REJECTED, ], ) +@pytest.mark.django_db def test_application_modified_at_non_draft(api_client, application, status): """ non-DRAFT application's last_modified_at is not visible to applicant @@ -1605,6 +1656,7 @@ def test_application_modified_at_non_draft(api_client, application, status): (PaySubsidyGranted.GRANTED, 50, 1, 400), # invalid percent ], ) +@pytest.mark.django_db def test_application_pay_subsidy( api_client, application, @@ -1627,6 +1679,7 @@ def test_application_pay_subsidy( assert BenefitType.SALARY_BENEFIT in response.data["available_benefit_types"] +@pytest.mark.django_db def test_attachment_upload_too_big(api_client, application, settings): settings.ENABLE_CLAMAV = False application.status = ApplicationStatus.DRAFT @@ -1654,6 +1707,7 @@ def test_attachment_upload_too_big(api_client, application, settings): assert len(application.attachments.all()) == 0 +@pytest.mark.django_db def test_attachment_upload_and_delete(api_client, application, settings): settings.ENABLE_CLAMAV = False image = Image.new("RGB", (100, 100)) @@ -1782,6 +1836,7 @@ def test_attachment_delete_unauthorized( (ApplicationStatus.REJECTED, 403), ], ) +@pytest.mark.django_db def test_attachment_delete(request, api_client, application, status, expected_code): application.status = status application.save() @@ -1811,6 +1866,7 @@ def test_attachment_delete(request, api_client, application, status, expected_co (ApplicationStatus.REJECTED, 403), ], ) +@pytest.mark.django_db def test_pdf_attachment_upload_and_download_as_applicant( request, api_client, application, status, upload_result, settings ): @@ -1847,6 +1903,7 @@ def test_pdf_attachment_upload_and_download_as_applicant( (ApplicationStatus.REJECTED, 403), ], ) +@pytest.mark.django_db def test_pdf_attachment_upload_and_download_as_handler( request, handler_api_client, application, status, upload_result, settings ): @@ -1911,6 +1968,7 @@ def test_attachment_download_unauthorized( ApplicationStatus.REJECTED, ], ) +@pytest.mark.django_db def test_attachment_upload_invalid_status(request, api_client, application, status): application.status = status application.save() @@ -1920,6 +1978,7 @@ def test_attachment_upload_invalid_status(request, api_client, application, stat @pytest.mark.parametrize("extension", ["pdf", "png", "jpg"]) +@pytest.mark.django_db def test_invalid_attachment_upload(api_client, application, extension, settings): settings.ENABLE_CLAMAV = False tmp_file = tempfile.NamedTemporaryFile(suffix=f".{extension}") @@ -1941,6 +2000,7 @@ def test_invalid_attachment_upload(api_client, application, extension, settings) assert len(application.attachments.all()) == 0 +@pytest.mark.django_db def test_too_many_attachments(request, api_client, application, settings): settings.ENABLE_CLAMAV = False for _ in range(AttachmentSerializer.MAX_ATTACHMENTS_PER_APPLICATION): @@ -1955,6 +2015,7 @@ def test_too_many_attachments(request, api_client, application, settings): ) +@pytest.mark.django_db def test_attachment_requirements( api_client, application, mock_get_organisation_roles_and_create_company ): @@ -1985,6 +2046,7 @@ def _submit_application(api_client, application): ) +@pytest.mark.django_db def test_attachment_validation(request, api_client, application, settings): settings.ENABLE_CLAMAV = False application.benefit_type = BenefitType.EMPLOYMENT_BENEFIT @@ -2027,6 +2089,7 @@ def test_attachment_validation(request, api_client, application, settings): assert application.status == ApplicationStatus.RECEIVED +@pytest.mark.django_db def test_purge_extra_attachments(request, api_client, application, settings): settings.ENABLE_CLAMAV = False application.benefit_type = BenefitType.SALARY_BENEFIT @@ -2058,6 +2121,7 @@ def test_purge_extra_attachments(request, api_client, application, settings): assert application.attachments.count() == 6 +@pytest.mark.django_db def test_employee_consent_upload(request, api_client, application, settings): settings.ENABLE_CLAMAV = False application.benefit_type = BenefitType.EMPLOYMENT_BENEFIT @@ -2126,6 +2190,7 @@ def test_employee_consent_upload(request, api_client, application, settings): assert application.status == ApplicationStatus.RECEIVED +@pytest.mark.django_db def test_application_number(api_client, application): assert Application.objects.count() == 1 @@ -2147,7 +2212,8 @@ def test_application_number(api_client, application): assert next_application.application_number == application.application_number + 2 -def test_application_api_before_accept_tos(api_client, application): +@pytest.mark.django_db +def test_application_api_before_accept_tos(api_client, application, terms_of_service): # Clear user TOS approval TermsOfServiceApproval.objects.all().delete() @@ -2202,6 +2268,7 @@ def test_application_api_before_accept_tos(api_client, application): ) +@pytest.mark.django_db def test_application_additional_information_needed_by(api_client, handling_application): response = api_client.get(get_detail_url(handling_application)) assert response.status_code == 200 @@ -2220,6 +2287,8 @@ def test_application_additional_information_needed_by(api_client, handling_appli assert response.data["additional_information_needed_by"] == date(2021, 12, 8) +@pytest.mark.django_db +@pytest.mark.freeze_time("2021-11-01") def test_application_status_last_changed_at(api_client, handling_application): with freeze_time("2021-12-01"): ApplicationLogEntry.objects.create( @@ -2230,10 +2299,11 @@ def test_application_status_last_changed_at(api_client, handling_application): response = api_client.get(get_detail_url(handling_application)) assert response.status_code == 200 assert response.data["status_last_changed_at"] == datetime( - 2021, 12, 1, tzinfo=timezone.utc + 2021, 12, 1, tzinfo=tz.utc ) +@pytest.mark.django_db def test_handler_application_default_ordering(handler_api_client): _create_random_applications() @@ -2268,6 +2338,7 @@ def _expected_sort_key(obj): assert expected_application_ids == returned_application_ids +@pytest.mark.django_db def test_handler_application_order_by(handler_api_client): _create_random_applications() key = "application_number" @@ -2303,6 +2374,7 @@ def _get_expected_sorting_values(key, reverse=False): assert expected_application_values == returned_application_values +@pytest.mark.django_db def test_handler_application_exlude_batched(handler_api_client): batch = ApplicationBatchFactory() apps = [DecidedApplicationFactory(), DecidedApplicationFactory()] @@ -2323,6 +2395,7 @@ def test_handler_application_exlude_batched(handler_api_client): assert len(response.data) == 1 +@pytest.mark.django_db def test_handler_application_filter_archived(handler_api_client): apps = [ DecidedApplicationFactory(), @@ -2347,6 +2420,7 @@ def test_handler_application_filter_archived(handler_api_client): assert response.data[0]["archived"] +@pytest.mark.django_db def test_application_pdf_print(api_client, application): settings.NEXT_PUBLIC_MOCK_FLAG = False # noqa @@ -2357,7 +2431,10 @@ def test_application_pdf_print(api_client, application): assert response.status_code == 200 -def test_application_pdf_print_denied(api_client, anonymous_client): +@pytest.mark.django_db +def test_application_pdf_print_denied( + api_client, anonymous_client, mock_get_organisation_roles_and_create_company +): settings.NEXT_PUBLIC_MOCK_FLAG = False # noqa application = DecidedApplicationFactory() @@ -2376,6 +2453,7 @@ def test_application_pdf_print_denied(api_client, anonymous_client): assert response.status_code == 403 +@pytest.mark.django_db def test_application_alterations(api_client, handler_api_client, application): cancelled_alteration = _create_application_alteration(application) cancelled_alteration.state = ApplicationAlterationState.CANCELLED @@ -2403,6 +2481,7 @@ def test_application_alterations(api_client, handler_api_client, application): assert len(response.data["alterations"]) == 3 +@pytest.mark.django_db def test_applications_with_unread_messages(api_client, handler_api_client, application): response = api_client.get( reverse("v1:handler-application-with-unread-messages"), @@ -2428,8 +2507,11 @@ def test_applications_with_unread_messages(api_client, handler_api_client, appli assert len(response.data) == 0 +@pytest.mark.django_db +@pytest.mark.freeze_time("2021-06-04") def test_require_additional_information(handler_api_client, application, mailoutbox): application.status = ApplicationStatus.HANDLING + application.applicant_language = "en" application.save() response = handler_api_client.patch( reverse( @@ -2462,7 +2544,7 @@ def _create_random_applications(): for _ in range(5): for class_name, status in combos: application = class_name() - random_datetime = f.past_datetime(tzinfo=timezone.utc) + random_datetime = f.past_datetime(tzinfo=tz.utc) application.log_entries.filter(to_status=status).update( created_at=random_datetime ) diff --git a/backend/benefit/applications/tests/test_applications_api_breaking_changes.py b/backend/benefit/applications/tests/test_applications_api_breaking_changes.py index b5e036a463..9df5dff34f 100644 --- a/backend/benefit/applications/tests/test_applications_api_breaking_changes.py +++ b/backend/benefit/applications/tests/test_applications_api_breaking_changes.py @@ -27,6 +27,7 @@ ), ], ) +@pytest.mark.django_db def test_application_break_association_business_activities( api_client, association_application, @@ -55,6 +56,7 @@ def test_application_break_association_business_activities( assert association_application.association_has_business_activities is False +@pytest.mark.django_db def test_application_break_de_minimis_aid(api_client, association_application): association_application.benefit_type = BenefitType.SALARY_BENEFIT association_application.association_has_business_activities = True @@ -82,6 +84,7 @@ def test_application_break_de_minimis_aid(api_client, association_application): assert association_application.association_has_business_activities is False +@pytest.mark.django_db def test_application_break_pay_subsidy_no_business_activities( api_client, association_application ): @@ -111,6 +114,7 @@ def test_application_break_pay_subsidy_no_business_activities( assert association_application.pay_subsidy_granted == PaySubsidyGranted.NOT_GRANTED +@pytest.mark.django_db def test_application_break_pay_subsidy_with_business_activities( api_client, association_application ): diff --git a/backend/benefit/applications/tests/test_applications_report.py b/backend/benefit/applications/tests/test_applications_report.py index 4cbf4a7366..64a9579626 100644 --- a/backend/benefit/applications/tests/test_applications_report.py +++ b/backend/benefit/applications/tests/test_applications_report.py @@ -29,24 +29,11 @@ check_csv_cell_list_lines_generator, check_csv_string_lines_generator, ) -from applications.tests.conftest import * # noqa from applications.tests.conftest import split_lines_at_semicolon from applications.tests.factories import DecidedApplicationFactory, DeMinimisAidFactory from calculator.enums import InstalmentStatus from calculator.models import Instalment from calculator.tests.factories import PaySubsidyFactory -from common.tests.conftest import * # noqa -from companies.tests.conftest import * # noqa -from helsinkibenefit.tests.conftest import * # noqa -from terms.tests.conftest import * # noqa - - -@pytest.fixture(autouse=True) -def run_before_and_after_tests(): - from applications.tests.before_after import before_test_reseed - - before_test_reseed([]) - yield def get_filenames_grouped_by_extension_from_zip( @@ -63,6 +50,7 @@ def _test_csv( csv_lines: List[List[str]], expected_application_numbers: List[int], expected_without_quotes: bool = False, + expected_with_bom: bool = False, ) -> None: # print(expected_without_quotes) if expected_without_quotes: @@ -72,6 +60,9 @@ def _test_csv( not_found_message = '"Ei löytynyt ehdot täyttäviä hakemuksia"' application_number = '"Hakemusnumero"' + if expected_with_bom: + application_number = f"\ufeff{application_number}" + if not expected_application_numbers: assert len(csv_lines) == 2 assert csv_lines[1][0] == not_found_message @@ -97,6 +88,7 @@ def _get_csv( expected_application_numbers: List[int], from_zip: bool = False, expected_without_quotes: bool = False, + expected_with_bom: bool = False, ) -> List[List[str]]: response = handler_api_client.get(url) assert response.status_code == 200 @@ -107,7 +99,12 @@ def _get_csv( assert isinstance(response, StreamingHttpResponse) csv_content: bytes = response.getvalue() csv_lines = split_lines_at_semicolon(csv_content.decode("utf-8")) - _test_csv(csv_lines, expected_application_numbers, expected_without_quotes) + _test_csv( + csv_lines, + expected_application_numbers=expected_application_numbers, + expected_without_quotes=expected_without_quotes, + expected_with_bom=expected_with_bom, + ) return csv_lines @@ -139,12 +136,7 @@ def _create_applications_for_export(): return (application1, application2, application3, application4) -@pytest.mark.skip( - reason=( - "This test fails in deploy pipeline - DETAIL: Key" - " (username)=(masonzachary_a45eb8) already exists." - ) -) +@pytest.mark.django_db def test_applications_csv_export_new_applications(handler_api_client): ( application1, @@ -160,6 +152,7 @@ def test_applications_csv_export_new_applications(handler_api_client): + "export_new_rejected_applications_csv_pdf/", [application3.application_number], from_zip=True, + expected_with_bom=True, ) assert ApplicationBatch.objects.all().count() == 1 assert ApplicationBatch.objects.all().first().applications.count() == 1 @@ -179,6 +172,7 @@ def test_applications_csv_export_new_applications(handler_api_client): [application1.application_number, application2.application_number], from_zip=True, expected_without_quotes=True, + expected_with_bom=True, ) assert ApplicationBatch.objects.all().count() == 2 assert set( @@ -201,6 +195,7 @@ def test_applications_csv_export_new_applications(handler_api_client): + "export_new_rejected_applications_csv_pdf/", [], from_zip=True, + expected_with_bom=True, ) _get_csv( handler_api_client, @@ -209,10 +204,12 @@ def test_applications_csv_export_new_applications(handler_api_client): [], from_zip=True, expected_without_quotes=True, + expected_with_bom=True, ) assert ApplicationBatch.objects.all().count() == 2 +@pytest.mark.django_db def test_application_alteration_csv_export( application_alteration, handler_api_client, decided_application ): @@ -355,6 +352,7 @@ def test_applications_csv_export_with_date_range(handler_api_client): ) +@pytest.mark.django_db def test_sensitive_data_removed_csv_output(sanitized_csv_service_with_one_application): csv_lines = split_lines_at_semicolon( sanitized_csv_service_with_one_application.get_csv_string() @@ -371,6 +369,7 @@ def test_sensitive_data_removed_csv_output(sanitized_csv_service_with_one_applic assert col_heading not in csv_lines[0] +@pytest.mark.django_db def test_power_bi_report_csv_output(application_powerbi_csv_service): csv_lines = split_lines_at_semicolon( application_powerbi_csv_service.get_csv_string() @@ -477,6 +476,7 @@ def test_power_bi_report_csv_output(application_powerbi_csv_service): ) +@pytest.mark.django_db def test_application_alteration_csv_output( application_alteration_csv_service, settings ): @@ -589,6 +589,7 @@ def test_application_alteration_csv_output( ) +@pytest.mark.django_db def test_write_application_alterations_csv_file( application_alteration_csv_service, tmp_path ): @@ -608,6 +609,7 @@ def test_write_application_alterations_csv_file( (True,), ], ) +@pytest.mark.django_db def test_talpa_applications_csv_output( talpa_applications_csv_service_with_one_application, instalments_enabled, settings ): @@ -682,6 +684,7 @@ def test_talpa_applications_csv_output( assert csv_lines[1][17] == f'"{application.batch.p2p_checker_name}"' +@pytest.mark.django_db def test_applications_csv_output(applications_csv_service): # noqa: C901 csv_lines = split_lines_at_semicolon(applications_csv_service.get_csv_string()) assert csv_lines[0][0] == '\ufeff"Hakemusnumero"' @@ -785,18 +788,21 @@ def test_applications_csv_output(applications_csv_service): # noqa: C901 ) +@pytest.mark.django_db def test_applications_csv_cell_list_lines_generator(applications_csv_service): check_csv_cell_list_lines_generator( applications_csv_service, expected_row_count_with_header=3 ) +@pytest.mark.django_db def test_applications_csv_string_lines_generator(applications_csv_service): check_csv_string_lines_generator( applications_csv_service, expected_row_count_with_header=3 ) +@pytest.mark.django_db def test_applications_csv_two_ahjo_rows( applications_csv_service_with_one_application, tmp_path ): @@ -917,6 +923,7 @@ def test_applications_csv_two_ahjo_rows( ) +@pytest.mark.django_db def test_applications_csv_too_many_de_minimis_aids( applications_csv_service_with_one_application, ): @@ -929,6 +936,7 @@ def test_applications_csv_too_many_de_minimis_aids( assert csv_lines[1][-1] == '"osa de minimis -tuista puuttuu raportilta"' +@pytest.mark.django_db def test_applications_csv_too_many_pay_subsidies( applications_csv_service_with_one_application, ): @@ -972,6 +980,7 @@ def test_applications_csv_too_many_pay_subsidies( ) +@pytest.mark.django_db def test_applications_csv_non_ascii_characters( applications_csv_service_with_one_application, ): @@ -985,6 +994,7 @@ def test_applications_csv_non_ascii_characters( assert csv_lines[1][12] == '"test äöÄÖtest"' # string is quoted +@pytest.mark.django_db def test_applications_csv_delimiter(applications_csv_service_with_one_application): application = applications_csv_service_with_one_application.get_applications()[0] application.company_name = "test;12" @@ -1004,6 +1014,7 @@ def test_applications_csv_missing_data(applications_csv_with_no_applications): assert csv_lines[1][0] == '"Ei löytynyt ehdot täyttäviä hakemuksia"' +@pytest.mark.django_db def test_applications_csv_monthly_amount_override( applications_csv_service_with_one_application, ): @@ -1030,6 +1041,7 @@ def test_applications_csv_monthly_amount_override( assert csv_lines[1][current_total_col] == "200.00" +@pytest.mark.django_db def test_write_applications_csv_file(applications_csv_service, tmp_path): application = applications_csv_service.get_applications()[0] application.company_name = "test äöÄÖtest" diff --git a/backend/benefit/applications/tests/test_command_import_archival_applications.py b/backend/benefit/applications/tests/test_command_import_archival_applications.py index 0fa11e41c3..85d98b63cf 100644 --- a/backend/benefit/applications/tests/test_command_import_archival_applications.py +++ b/backend/benefit/applications/tests/test_command_import_archival_applications.py @@ -1,5 +1,6 @@ from datetime import date +import pytest from django.core.management import call_command from applications.models import ArchivalApplication @@ -142,6 +143,7 @@ def create_companies_for_archival_applications(): company.save() +@pytest.mark.django_db def test_import_archival_applications(): assert ArchivalApplication.objects.all().count() == 0 ImportArchivalApplicationsTestUtility.create_companies_for_archival_applications() @@ -150,7 +152,7 @@ def test_import_archival_applications(): assert ArchivalApplication.objects.all().count() == len( ImportArchivalApplicationsTestUtility.test_data["values"] ) - assert Company.objects.all().count() == 3 + assert Company.objects.all().count() == 2 # Assert the values of the retrieved archival application app = ArchivalApplication.objects.filter(application_number="R001").first() diff --git a/backend/benefit/applications/tests/test_command_set_as_archived.py b/backend/benefit/applications/tests/test_command_set_as_archived.py index 003e927c9d..b21e140e2e 100644 --- a/backend/benefit/applications/tests/test_command_set_as_archived.py +++ b/backend/benefit/applications/tests/test_command_set_as_archived.py @@ -1,3 +1,4 @@ +import pytest from django.core.management import call_command from applications.api.v1.serializers.application import HandlerApplicationSerializer @@ -33,6 +34,7 @@ } +@pytest.mark.django_db def test_decision_proposal_drafting(): # Create a new application, set the other one as cancelled application = ReceivedApplicationFactory(application_number=100002) diff --git a/backend/benefit/applications/tests/test_models.py b/backend/benefit/applications/tests/test_models.py index b829d0da36..e27434f04a 100755 --- a/backend/benefit/applications/tests/test_models.py +++ b/backend/benefit/applications/tests/test_models.py @@ -12,17 +12,20 @@ from helsinkibenefit.tests.conftest import * # noqa +@pytest.mark.django_db def test_application_model(application): assert Application.objects.count() == 1 assert application.employee +@pytest.mark.django_db def test_employee_model(employee): assert Employee.objects.count() == 1 assert Application.objects.count() == 1 assert employee.application +@pytest.mark.django_db def test_application_batch(application_batch): assert ApplicationBatch.objects.count() == 1 assert application_batch.applications.count() == 2 @@ -38,6 +41,7 @@ def test_application_batch(application_batch): (ApplicationBatchStatus.DECIDED_REJECTED, AhjoDecision.DECIDED_REJECTED), ], ) +@pytest.mark.django_db def test_application_batch_update_after_details_request( application_with_ahjo_decision, batch_status, @@ -79,6 +83,7 @@ def test_application_batch_update_after_details_request( (ApplicationBatchStatus.COMPLETED, AhjoDecision.DECIDED_ACCEPTED), ], ) +@pytest.mark.django_db def test_application_batch_ahjo_decision(application_batch, status, expected_result): application_batch.status = status if status in [ @@ -120,6 +125,7 @@ def test_application_batch_ahjo_decision(application_batch, status, expected_res (ApplicationBatchStatus.COMPLETED, False), ], ) +@pytest.mark.django_db def test_application_batch_modified(application_batch, status, expected_result): application_batch.status = status if status in [ @@ -144,6 +150,7 @@ def test_application_batch_modified(application_batch, status, expected_result): assert application_batch.applications_can_be_modified == expected_result +@pytest.mark.django_db def test_application_address(application): application.official_company_city = "official city" application.alternative_company_city = "alternative city" @@ -172,6 +179,7 @@ def test_application_address(application): ) +@pytest.mark.django_db def test_encrypted_searchable_social_security_number(employee): # test exact ssn searches on the hashed field assert employee.social_security_number diff --git a/backend/benefit/applications/tests/test_power_bi_integration.py b/backend/benefit/applications/tests/test_power_bi_integration.py index ff76cf73ff..30d7e76bb6 100644 --- a/backend/benefit/applications/tests/test_power_bi_integration.py +++ b/backend/benefit/applications/tests/test_power_bi_integration.py @@ -1,9 +1,11 @@ import csv from io import StringIO +import pytest from django.urls import reverse +@pytest.mark.django_db def test_get_power_bi_data(power_bi_client, decided_application_with_decision_date): batch = decided_application_with_decision_date.batch url = ( diff --git a/backend/benefit/applications/tests/test_serializers.py b/backend/benefit/applications/tests/test_serializers.py index 3e9ccb9ab5..4bc1df8e78 100644 --- a/backend/benefit/applications/tests/test_serializers.py +++ b/backend/benefit/applications/tests/test_serializers.py @@ -5,6 +5,7 @@ BaseApplicationSerializer, HandlerApplicationSerializer, ) +from companies.tests.factories import CompanyFactory @pytest.mark.parametrize("next_public_mock_flag", [False, True]) @@ -12,6 +13,7 @@ "application_serializer", [ApplicantApplicationSerializer, HandlerApplicationSerializer], ) +@pytest.mark.django_db def test_logged_in_user_is_admin_with_anonymous_user( settings, anonymous_client, @@ -27,12 +29,14 @@ def test_logged_in_user_is_admin_with_anonymous_user( "application_serializer", [ApplicantApplicationSerializer, HandlerApplicationSerializer], ) +@pytest.mark.django_db def test_get_logged_in_user_company_with_anonymous_user( settings, anonymous_client, next_public_mock_flag: bool, application_serializer: BaseApplicationSerializer, ): + CompanyFactory() settings.NEXT_PUBLIC_MOCK_FLAG = next_public_mock_flag assert ( application_serializer().get_logged_in_user_company() is not None diff --git a/backend/benefit/applications/tests/test_talpa_integration.py b/backend/benefit/applications/tests/test_talpa_integration.py index 93a00fbdd1..5634510984 100644 --- a/backend/benefit/applications/tests/test_talpa_integration.py +++ b/backend/benefit/applications/tests/test_talpa_integration.py @@ -1,8 +1,9 @@ import decimal -from datetime import datetime, timedelta, timezone +from datetime import timedelta import pytest from django.urls import reverse +from django.utils import timezone from applications.enums import ( ApplicationBatchStatus, @@ -14,15 +15,13 @@ check_csv_cell_list_lines_generator, check_csv_string_lines_generator, ) -from applications.tests.conftest import * # noqa from applications.tests.conftest import split_lines_at_semicolon from calculator.enums import InstalmentStatus from calculator.models import Instalment -from common.tests.conftest import * # noqa -from helsinkibenefit.tests.conftest import * # noqa from shared.audit_log.models import AuditLogEntry +@pytest.mark.django_db def test_talpa_lines(applications_csv_service): csv_lines = list(applications_csv_service.get_csv_cell_list_lines_generator()) assert applications_csv_service.applications.count() == 2 @@ -36,12 +35,14 @@ def test_talpa_lines(applications_csv_service): ) +@pytest.mark.django_db def test_talpa_csv_cell_list_lines_generator(talpa_applications_csv_service): check_csv_cell_list_lines_generator( talpa_applications_csv_service, expected_row_count_with_header=3 ) +@pytest.mark.django_db def test_talpa_csv_string_lines_generator(talpa_applications_csv_service): check_csv_string_lines_generator( talpa_applications_csv_service, expected_row_count_with_header=3 @@ -56,6 +57,7 @@ def test_talpa_csv_string_lines_generator(talpa_applications_csv_service): (True, 2), ], ) +@pytest.mark.django_db def test_talpa_csv_output( talpa_applications_csv_service_with_one_application, instalments_enabled, @@ -71,7 +73,7 @@ def test_talpa_csv_output( if instalments_enabled: for i in range(number_of_instalments): status = InstalmentStatus.ACCEPTED - due_date = datetime.now(timezone.utc).date() + due_date = timezone.now().date() if i == 1: status = InstalmentStatus.WAITING due_date = timezone.now() + timedelta(days=181) @@ -111,6 +113,7 @@ def test_talpa_csv_output( ) +@pytest.mark.django_db def test_talpa_csv_non_ascii_characters( talpa_applications_csv_service_with_one_application, ): @@ -125,6 +128,7 @@ def test_talpa_csv_non_ascii_characters( assert csv_lines[1][3] == '"test äöÄÖtest"' # string is quoted +@pytest.mark.django_db def test_talpa_csv_delimiter(talpa_applications_csv_service_with_one_application): application = ( talpa_applications_csv_service_with_one_application.applications.first() @@ -144,6 +148,7 @@ def test_talpa_csv_delimiter(talpa_applications_csv_service_with_one_application (True,), ], ) +@pytest.mark.django_db def test_talpa_csv_decimal( talpa_applications_csv_service_with_one_application, settings, @@ -161,7 +166,7 @@ def test_talpa_csv_decimal( amount_paid=decimal.Decimal("123.45"), instalment_number=1, status=InstalmentStatus.ACCEPTED, - due_date=datetime.now(timezone.utc).date(), + due_date=timezone.now().date(), ) else: application.calculation.calculated_benefit_amount = decimal.Decimal("123.45") @@ -173,11 +178,12 @@ def test_talpa_csv_decimal( assert csv_lines[1][8] == "123.45" +@pytest.mark.django_db def test_talpa_csv_date(talpa_applications_csv_service_with_one_application): application = ( talpa_applications_csv_service_with_one_application.get_applications().first() ) - now = datetime.now(timezone.utc) + now = timezone.now() application.batch.decision_date = now application.batch.save() csv_lines = split_lines_at_semicolon( @@ -186,6 +192,7 @@ def test_talpa_csv_date(talpa_applications_csv_service_with_one_application): assert csv_lines[1][12] == f'"{now.strftime("%Y-%m-%d")}"' +@pytest.mark.django_db def test_write_talpa_csv_file( talpa_applications_csv_service_with_one_application, tmp_path ): @@ -202,6 +209,7 @@ def test_write_talpa_csv_file( assert "äöÄÖtest" in contents +@pytest.mark.django_db def test_talpa_callback_is_disabled( talpa_client, settings, @@ -250,7 +258,7 @@ def test_talpa_callback_success( if instalments_enabled: for i in range(number_of_instalments): status = InstalmentStatus.ACCEPTED - due_date = datetime.now(timezone.utc).date() + due_date = timezone.now().date() if i == 1: status = InstalmentStatus.WAITING due_date = timezone.now() + timedelta(days=181) @@ -368,7 +376,7 @@ def test_talpa_callback_rejected_application( if instalments_enabled: for i in range(number_of_instalments): - due_date = datetime.now(timezone.utc).date() + due_date = timezone.now().date() if i == 1: due_date = timezone.now() + timedelta(days=181) @@ -428,6 +436,7 @@ def test_talpa_callback_rejected_application( (ApplicationStatus.ADDITIONAL_INFORMATION_NEEDED), ], ) +@pytest.mark.django_db def test_talpa_csv_applications_query( multiple_decided_applications, application_status, settings ): @@ -441,7 +450,7 @@ def test_talpa_csv_applications_query( amount=decimal.Decimal("123.45"), instalment_number=1, status=InstalmentStatus.ACCEPTED, - due_date=datetime.now(timezone.utc).date(), + due_date=timezone.now().date(), ) app.status = application_status diff --git a/backend/benefit/applications/tests/test_view_sets.py b/backend/benefit/applications/tests/test_view_sets.py index 581722bace..2cf4ebf14c 100644 --- a/backend/benefit/applications/tests/test_view_sets.py +++ b/backend/benefit/applications/tests/test_view_sets.py @@ -7,6 +7,7 @@ @pytest.mark.parametrize("next_public_mock_flag", [False, True]) +@pytest.mark.django_db def test_applicant_application_view_set_get_queryset_with_anonymous_user( settings, next_public_mock_flag: bool, diff --git a/backend/benefit/calculator/rules.py b/backend/benefit/calculator/rules.py index 5d0a7e2ee1..6541ef5994 100644 --- a/backend/benefit/calculator/rules.py +++ b/backend/benefit/calculator/rules.py @@ -2,7 +2,6 @@ import datetime import decimal import logging -from typing import Union from django.conf import settings from django.db import transaction @@ -59,7 +58,7 @@ def _get_change_days( def _get_item_in_effect( self, items: list[PaySubsidy], day: datetime.date - ) -> Union[PaySubsidy, None]: + ) -> PaySubsidy | None: """Return the first item in the list whose start date is less than or equal to the given day, and whose end date is greater than or equal to the given day. If no such item is found, it returns None.""" # noqa: E501 diff --git a/backend/benefit/calculator/tests/factories.py b/backend/benefit/calculator/tests/factories.py index 384aa7531d..1e4eb8b870 100644 --- a/backend/benefit/calculator/tests/factories.py +++ b/backend/benefit/calculator/tests/factories.py @@ -120,6 +120,7 @@ class CalculationFactory(factory.django.DjangoModelFactory): class Meta: model = Calculation + skip_postgeneration_save = True class PreviousBenefitFactory(factory.django.DjangoModelFactory): diff --git a/backend/benefit/calculator/tests/test_calculator_api.py b/backend/benefit/calculator/tests/test_calculator_api.py index bd9366e0c1..07438d3039 100644 --- a/backend/benefit/calculator/tests/test_calculator_api.py +++ b/backend/benefit/calculator/tests/test_calculator_api.py @@ -92,6 +92,7 @@ def test_application_retrieve_calculation_as_handler( ) == to_decimal(duration_in_months(pay_subsidy.start_date, pay_subsidy.end_date), 2) +@pytest.mark.django_db def test_application_try_retrieve_calculation_as_applicant(api_client, application): response = api_client.get(get_detail_url(application)) assert "calculation" not in response.data @@ -100,6 +101,7 @@ def test_application_try_retrieve_calculation_as_applicant(api_client, applicati assert response.status_code == 200 +@pytest.mark.django_db def test_application_create_calculation_on_submit( request, api_client, @@ -703,6 +705,9 @@ def test_application_calculation_rows_id_exists( "number_of_instalments, has_subsidies", [(2, False), (1, True)], ) +@pytest.mark.django_db +# Doesn't work without freezing time as otherwise first instalment limit is not reached +@pytest.mark.freeze_time("2024-01-01") def test_application_calculation_instalments( handling_application, settings, number_of_instalments, has_subsidies ): diff --git a/backend/benefit/calculator/tests/test_handler_excel_examples.py b/backend/benefit/calculator/tests/test_handler_excel_examples.py index df73834d8a..9807a8a439 100644 --- a/backend/benefit/calculator/tests/test_handler_excel_examples.py +++ b/backend/benefit/calculator/tests/test_handler_excel_examples.py @@ -1,6 +1,7 @@ import datetime import os +import pytest from openpyxl import load_workbook from openpyxl.utils import get_column_letter @@ -279,6 +280,7 @@ def _verify_results(self): ] +@pytest.mark.django_db def test_cases_from_excel(request, api_client): excel_file_name = os.path.join( request.fspath.dirname, "Helsinki-lisa laskurin testitapaukset.xlsx" diff --git a/backend/benefit/calculator/tests/test_manual_override.py b/backend/benefit/calculator/tests/test_manual_override.py index 4177635c5c..6fe2f0c954 100644 --- a/backend/benefit/calculator/tests/test_manual_override.py +++ b/backend/benefit/calculator/tests/test_manual_override.py @@ -20,6 +20,7 @@ (date(2022, 1, 1), date(2022, 12, 31), 100, 1200), ], ) +@pytest.mark.django_db def test_override_monthly_benefit_amount( handling_application, start_date, diff --git a/backend/benefit/calculator/tests/test_models.py b/backend/benefit/calculator/tests/test_models.py index b5408714ef..c7f54c41db 100644 --- a/backend/benefit/calculator/tests/test_models.py +++ b/backend/benefit/calculator/tests/test_models.py @@ -15,6 +15,7 @@ from helsinkibenefit.tests.conftest import * # noqa +@pytest.mark.django_db def test_calculation_model(calculation): assert Calculation.objects.count() == 1 assert calculation.application @@ -28,21 +29,25 @@ def test_calculation_model(calculation): assert calculation.duration_in_months_rounded is None +@pytest.mark.django_db def test_pay_subsidy(pay_subsidy): assert PaySubsidy.objects.count() == 1 assert pay_subsidy.application +@pytest.mark.django_db def test_training_compensation(training_compensation): assert TrainingCompensation.objects.count() == 1 assert training_compensation.application +@pytest.mark.django_db def test_previous_benefit(previous_benefit): assert PreviousBenefit.objects.count() == 1 assert previous_benefit.company +@pytest.mark.django_db def test_create_for_application(application): application.status = ApplicationStatus.RECEIVED calculation = Calculation.objects.create_for_application(application) @@ -50,6 +55,7 @@ def test_create_for_application(application): assert calculation.monthly_pay == application.employee.monthly_pay +@pytest.mark.django_db def test_create_for_application_with_pay_subsidies(application): application.status = ApplicationStatus.RECEIVED application.pay_subsidy_granted = True @@ -67,6 +73,7 @@ def test_create_for_application_with_pay_subsidies(application): assert pay_subsidy.disability_or_illness is False +@pytest.mark.django_db def test_create_for_application_fail(received_application): # calculation already exists with pytest.raises(BenefitAPIException): @@ -76,6 +83,7 @@ def test_create_for_application_fail(received_application): @pytest.mark.parametrize( "pay_subsidy_percent, max_subsidy", [(70, 1770), (50, 1260), (100, 2020)] ) +@pytest.mark.django_db def test_pay_subsidy_maximum(handling_application, pay_subsidy_percent, max_subsidy): assert handling_application.pay_subsidies.count() == 1 handling_application.pay_subsidies.all().update( @@ -216,6 +224,7 @@ def test_pay_subsidy_maximum(handling_application, pay_subsidy_percent, max_subs ), ], ) +@pytest.mark.django_db def test_calculation_required_fields( handling_application, benefit_type, diff --git a/backend/benefit/calculator/tests/test_previous_benefits_api.py b/backend/benefit/calculator/tests/test_previous_benefits_api.py index 06aa5fd60d..3009cd1d62 100644 --- a/backend/benefit/calculator/tests/test_previous_benefits_api.py +++ b/backend/benefit/calculator/tests/test_previous_benefits_api.py @@ -1,5 +1,6 @@ import decimal +import pytest from rest_framework.reverse import reverse from applications.tests.conftest import * # noqa @@ -12,16 +13,19 @@ def get_previous_benefits_detail_url(previous_benefit): return reverse("v1:previousbenefit-detail", kwargs={"pk": previous_benefit.id}) +@pytest.mark.django_db def test_get_previous_benefit_unauthenticated(anonymous_client, previous_benefit): response = anonymous_client.get(get_previous_benefits_detail_url(previous_benefit)) assert response.status_code == 403 +@pytest.mark.django_db def test_get_previous_benefit_applicant(api_client, previous_benefit): response = api_client.get(get_previous_benefits_detail_url(previous_benefit)) assert response.status_code == 403 +@pytest.mark.django_db def test_get_previous_benefit_handler(handler_api_client, previous_benefit): response = handler_api_client.get( get_previous_benefits_detail_url(previous_benefit) @@ -35,22 +39,26 @@ def test_get_previous_benefit_handler(handler_api_client, previous_benefit): assert response.status_code == 200 +@pytest.mark.django_db def test_previous_benefits_list(handler_api_client, previous_benefit): response = handler_api_client.get(reverse("v1:previousbenefit-list")) assert len(response.data) == 1 assert response.status_code == 200 +@pytest.mark.django_db def test_previous_benefits_list_as_applicant(api_client, previous_benefit): response = api_client.get(reverse("v1:previousbenefit-list")) assert response.status_code == 403 +@pytest.mark.django_db def test_previous_benefits_list_unauthenticated(anonymous_client, previous_benefit): response = anonymous_client.get(reverse("v1:previousbenefit-list")) assert response.status_code == 403 +@pytest.mark.django_db def test_filter_previous_benefit_by_ssn(handler_api_client, previous_benefit): benefit2 = PreviousBenefitFactory(social_security_number="123456-7890") assert previous_benefit.social_security_number != benefit2.social_security_number @@ -64,6 +72,7 @@ def test_filter_previous_benefit_by_ssn(handler_api_client, previous_benefit): assert response.status_code == 200 +@pytest.mark.django_db def test_create_previous_benefit(handler_api_client, previous_benefit): data = PreviousBenefitSerializer(previous_benefit).data previous_benefit.delete() @@ -76,6 +85,7 @@ def test_create_previous_benefit(handler_api_client, previous_benefit): assert response.status_code == 201 +@pytest.mark.django_db def test_create_previous_benefit_unauthenticated(anonymous_client, previous_benefit): data = PreviousBenefitSerializer(previous_benefit).data previous_benefit.delete() @@ -88,6 +98,7 @@ def test_create_previous_benefit_unauthenticated(anonymous_client, previous_bene assert response.status_code == 403 +@pytest.mark.django_db def test_create_previous_benefit_as_applicant(api_client, previous_benefit): data = PreviousBenefitSerializer(previous_benefit).data previous_benefit.delete() @@ -100,6 +111,7 @@ def test_create_previous_benefit_as_applicant(api_client, previous_benefit): assert response.status_code == 403 +@pytest.mark.django_db def test_previous_benefit_put_as_applicant(api_client, previous_benefit): data = PreviousBenefitSerializer(previous_benefit).data data["monthly_amount"] = "1234.56" @@ -110,6 +122,7 @@ def test_previous_benefit_put_as_applicant(api_client, previous_benefit): assert response.status_code == 403 +@pytest.mark.django_db def test_previous_benefit_put_unauthenticated(anonymous_client, previous_benefit): data = PreviousBenefitSerializer(previous_benefit).data data["monthly_amount"] = "1234.56" @@ -120,6 +133,7 @@ def test_previous_benefit_put_unauthenticated(anonymous_client, previous_benefit assert response.status_code == 403 +@pytest.mark.django_db def test_previous_benefit_put(handler_api_client, previous_benefit): """ modify existing previous_benefit @@ -136,6 +150,7 @@ def test_previous_benefit_put(handler_api_client, previous_benefit): assert previous_benefit.monthly_amount == decimal.Decimal("1234.56") +@pytest.mark.django_db def test_previous_benefit_delete(handler_api_client, previous_benefit): """ modify existing previous_benefit @@ -148,6 +163,7 @@ def test_previous_benefit_delete(handler_api_client, previous_benefit): assert PreviousBenefit.objects.all().count() == 0 +@pytest.mark.django_db def test_previous_benefit_delete_unauthenticated(anonymous_client, previous_benefit): """ modify existing previous_benefit @@ -159,6 +175,7 @@ def test_previous_benefit_delete_unauthenticated(anonymous_client, previous_bene assert PreviousBenefit.objects.all().count() == 1 +@pytest.mark.django_db def test_previous_benefit_delete_as_applicant(api_client, previous_benefit): """ modify existing previous_benefit diff --git a/backend/benefit/common/authentications.py b/backend/benefit/common/authentications.py index 8a24290412..7d4f15d1f8 100644 --- a/backend/benefit/common/authentications.py +++ b/backend/benefit/common/authentications.py @@ -37,7 +37,7 @@ def authenticate(self, request): raise AuthenticationFailed("Invalid username/password.") def authenticate_header(self, request): - return 'Basic realm="{}"'.format(self.www_authenticate_realm) + return f'Basic realm="{self.www_authenticate_realm}"' class PowerBiAuthentication(RobotBasicAuthentication): diff --git a/backend/benefit/common/delay_call.py b/backend/benefit/common/delay_call.py index d62ba609b9..4f4875f09a 100644 --- a/backend/benefit/common/delay_call.py +++ b/backend/benefit/common/delay_call.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import collections import contextlib import threading diff --git a/backend/benefit/common/localized_iban_field.py b/backend/benefit/common/localized_iban_field.py index 42c69624bb..e0bc558756 100644 --- a/backend/benefit/common/localized_iban_field.py +++ b/backend/benefit/common/localized_iban_field.py @@ -6,13 +6,11 @@ class LocalizedIBANValidator(IBANValidator): def __init__(self, use_nordea_extensions=False, include_countries=None): - super(LocalizedIBANValidator, self).__init__( - use_nordea_extensions, include_countries - ) + super().__init__(use_nordea_extensions, include_countries) def __call__(self, value): try: - super(LocalizedIBANValidator, self).__call__(value) + super().__call__(value) except ValidationError as e: raise ValidationError(_("Invalid IBAN")) from e diff --git a/backend/benefit/common/tests/conftest.py b/backend/benefit/common/tests/conftest.py index 5957b12dce..66774fc191 100644 --- a/backend/benefit/common/tests/conftest.py +++ b/backend/benefit/common/tests/conftest.py @@ -5,8 +5,6 @@ import pytest from django.contrib.auth.models import Permission from django.utils.translation import activate -from freezegun import freeze_time -from langdetect import DetectorFactory from rest_framework.authtoken.models import Token from rest_framework.test import APIClient @@ -25,12 +23,7 @@ def setup_test_environment(settings): settings.LANGUAGE_CODE = "fi" settings.DISABLE_TOS_APPROVAL_CHECK = False settings.NEXT_PUBLIC_MOCK_FLAG = False - factory.random.reseed_random("777") - DetectorFactory.seed = 0 - random.seed(777) activate("en") - with freeze_time("2021-06-04"): - yield @pytest.fixture diff --git a/backend/benefit/common/utils.py b/backend/benefit/common/utils.py index 8b2bc17aff..9f11f7c20f 100644 --- a/backend/benefit/common/utils.py +++ b/backend/benefit/common/utils.py @@ -2,8 +2,8 @@ import functools import hashlib import itertools +from collections.abc import Iterator from datetime import date, timedelta -from typing import Iterator, Tuple, Union from dateutil.relativedelta import relativedelta from django.core.files import File @@ -45,7 +45,7 @@ def update_object(obj: object, data: dict, limit_to_fields=None): obj.save() -def xgroup(iter, n=2, check_length=False) -> Iterator[Tuple]: +def xgroup(iter, n=2, check_length=False) -> Iterator[tuple]: """ Groups elements from an iterable into tuples of size n. @@ -173,7 +173,7 @@ def _getattr(obj, attr): return functools.reduce(_getattr, [obj] + attr.split(".")) -def to_decimal(numeric_value, decimal_places: Union[int, None] = None, allow_null=True): +def to_decimal(numeric_value, decimal_places: int | None = None, allow_null=True): """ Converts a numeric value to a decimal. @@ -210,7 +210,7 @@ class PhoneNumberField(DefaultPhoneNumberField): def to_representation(self, value): if not value: return "" - return "0{}".format(value.national_number) + return f"0{value.national_number}" def date_range_overlap(start_1: date, end_1: date, start_2: date, end_2: date): @@ -325,7 +325,7 @@ def days360(start_date: date, end_date: date): def duration_in_months( - start_date: date, end_date: date, decimal_places: Union[int, None] = None + start_date: date, end_date: date, decimal_places: int | None = None ): # This is the formula used in the application calculator Excel file 2021-09 return to_decimal( @@ -352,7 +352,7 @@ def duration_in_months_rounded(self): # in many calculations return self._get_duration_in_months(decimal_places=2) - def _get_duration_in_months(self, decimal_places: Union[int, None] = None): + def _get_duration_in_months(self, decimal_places: int | None = None): if self.start_date and self.end_date: return duration_in_months( self.start_date, self.end_date, decimal_places=decimal_places @@ -447,7 +447,7 @@ def hash_file(file: File) -> str: return sha256.hexdigest() -def get_request_ip_address(request: HttpRequest) -> Union[str, None]: +def get_request_ip_address(request: HttpRequest) -> str | None: """Get the IP address of a request""" x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") if x_forwarded_for: diff --git a/backend/benefit/docker-entrypoint.sh b/backend/benefit/docker-entrypoint.sh index d74786f588..81422b6350 100755 --- a/backend/benefit/docker-entrypoint.sh +++ b/backend/benefit/docker-entrypoint.sh @@ -3,7 +3,7 @@ set -e # Wait for the database -if [ -z "$SKIP_DATABASE_CHECK" ] || [ "$SKIP_DATABASE_CHECK" = "0" ]; then +if [[ -z "$SKIP_DATABASE_CHECK" ]] || [[ "$SKIP_DATABASE_CHECK" = "0" ]]; then until nc -z -v -w30 "${DATABASE_HOST}" "${DATABASE_PORT-5432}" do echo "Waiting for postgres database connection..." diff --git a/backend/benefit/helsinkibenefit/gdpr.py b/backend/benefit/helsinkibenefit/gdpr.py new file mode 100644 index 0000000000..f603a75649 --- /dev/null +++ b/backend/benefit/helsinkibenefit/gdpr.py @@ -0,0 +1,17 @@ +def get_user(user): + """ + Function used by the Helsinki Profile GDPR API to get the "user" instance from the + "GDPR Model" instance. Since in our case the GDPR Model and the user are one and + the same, we simply return the same User instance that is given as a parameter. + + :param user: the User instance whose GDPR data is being queried + :return: the same User instance + """ + return user + + +def deleter(obj, dry_run): + """ + Simple deleter + """ + obj.delete() diff --git a/backend/benefit/helsinkibenefit/settings.py b/backend/benefit/helsinkibenefit/settings.py index 203d3583e1..34b530a511 100644 --- a/backend/benefit/helsinkibenefit/settings.py +++ b/backend/benefit/helsinkibenefit/settings.py @@ -42,9 +42,9 @@ SENTRY_ENVIRONMENT=(str, ""), SENTRY_ATTACH_STACKTRACE=(bool, False), SENTRY_MAX_BREADCRUMBS=(int, 0), - SENTRY_REQUEST_BODIES=(str, "never"), + SENTRY_MAX_REQUEST_BODY_SIZE=(str, "never"), SENTRY_SEND_DEFAULT_PII=(bool, False), - SENTRY_WITH_LOCALS=(bool, False), + SENTRY_INCLUDE_LOCAL_VARIABLES=(bool, False), SENTRY_RELEASE=(str, ""), CORS_ALLOWED_ORIGINS=(list, []), CORS_ALLOW_ALL_ORIGINS=(bool, False), @@ -225,9 +225,9 @@ sentry_sdk.init( attach_stacktrace=env.bool("SENTRY_ATTACH_STACKTRACE"), max_breadcrumbs=env.int("SENTRY_MAX_BREADCRUMBS"), - request_bodies=env.str("SENTRY_REQUEST_BODIES"), + max_request_body_size=env.str("SENTRY_MAX_REQUEST_BODY_SIZE"), send_default_pii=env.bool("SENTRY_SEND_DEFAULT_PII"), - with_locals=env.bool("SENTRY_WITH_LOCALS"), + include_local_variables=env.bool("SENTRY_INCLUDE_LOCAL_VARIABLES"), dsn=env.str("SENTRY_DSN"), release=env.str("SENTRY_RELEASE"), environment=env.str("SENTRY_ENVIRONMENT"), @@ -546,7 +546,10 @@ GDPR_API_QUERY_SCOPE = env("GDPR_API_QUERY_SCOPE") GDPR_API_DELETE_SCOPE = env("GDPR_API_DELETE_SCOPE") +GDPR_API_DELETER = "helsinkibenefit.gdpr.deleter" GDPR_API_MODEL = "users.User" +GDPR_API_MODEL_LOOKUP = "username" +GDPR_API_USER_PROVIDER = "helsinkibenefit.gdpr.get_user" # local_settings.py can be used to override environment-specific settings # like database and email that differ between development and production. diff --git a/backend/benefit/helsinkibenefit/tests/conftest.py b/backend/benefit/helsinkibenefit/tests/conftest.py index eb447d0d55..1566827e2e 100644 --- a/backend/benefit/helsinkibenefit/tests/conftest.py +++ b/backend/benefit/helsinkibenefit/tests/conftest.py @@ -2,17 +2,7 @@ from django.core.management import call_command -@pytest.fixture(autouse=True) -def autouse_django_db(db, django_db_setup, django_db_blocker): - pass - - @pytest.fixture(scope="session") def django_db_setup(django_db_setup, django_db_blocker): with django_db_blocker.unblock(): call_command("loaddata", "groups.json") - - -@pytest.fixture(scope="session") -def django_db_modify_db_settings(): - pass diff --git a/backend/benefit/helsinkibenefit/urls.py b/backend/benefit/helsinkibenefit/urls.py index 2e2c99164a..029586cf67 100644 --- a/backend/benefit/helsinkibenefit/urls.py +++ b/backend/benefit/helsinkibenefit/urls.py @@ -42,7 +42,7 @@ HandlerNoteViewSet, ) from terms.api.v1.views import ApproveTermsOfServiceView -from users.api.v1.views import CurrentUserView, UserOptionsView, UserUuidGDPRAPIView +from users.api.v1.views import CurrentUserView, UserOptionsView router = routers.DefaultRouter() router.register( @@ -133,7 +133,7 @@ AhjoApplicationView.as_view(), name="ahjo_application_url", ), - path("gdpr-api/v1/user/", UserUuidGDPRAPIView.as_view(), name="gdpr_v1"), + path("gdpr-api/", include("helsinki_gdpr.urls")), path("v1/", include((router.urls, "v1"), namespace="v1")), path("v1/", include(applicant_app_router.urls)), path("v1/", include(handler_app_router.urls)), diff --git a/backend/benefit/messages/tests/test_api.py b/backend/benefit/messages/tests/test_api.py index adf78bc03c..9110db689a 100644 --- a/backend/benefit/messages/tests/test_api.py +++ b/backend/benefit/messages/tests/test_api.py @@ -36,6 +36,7 @@ "handler-note-list", ], ) +@pytest.mark.django_db def test_list_message_unauthenticated( anonymous_client, handling_application, view_name ): @@ -53,8 +54,12 @@ def test_list_message_unauthenticated( "handler-note-list", ], ) +@pytest.mark.django_db def test_list_message_unauthorized( - api_client, anonymous_handling_application, view_name + api_client, + anonymous_handling_application, + view_name, + mock_get_organisation_roles_and_create_company, ): if view_name == "applicant-message-list": # Cannot see application they doesn't belong to @@ -81,6 +86,7 @@ def test_list_message_unauthorized( "handler-message-list", ], ) +@pytest.mark.django_db def test_list_messages( api_client, handler_api_client, @@ -144,6 +150,7 @@ def test_list_notes(handler_api_client, handling_application): "handler-note-list", ], ) +@pytest.mark.django_db def test_create_message_unauthenticated( anonymous_client, handling_application, view_name ): @@ -162,6 +169,7 @@ def test_create_message_unauthenticated( "handler-note-list", ], ) +@pytest.mark.django_db def test_create_message_unauthorized(api_client, handling_application, view_name): handling_application.company = CompanyFactory() handling_application.save() @@ -178,6 +186,7 @@ def test_create_message_unauthorized(api_client, handling_application, view_name assert result.status_code == 403 +@pytest.mark.django_db def test_create_applicant_message_invalid( api_client, application, mock_get_organisation_roles_and_create_company ): @@ -250,6 +259,7 @@ def test_create_handler_message_invalid(handler_api_client, handling_application (ApplicationStatus.CANCELLED, False, 400), ], ) +@pytest.mark.django_db def test_applicant_send_first_message( api_client, handling_application, @@ -283,6 +293,7 @@ def test_applicant_send_first_message( ("handler-note-list", MessageType.NOTE, None), ], ) +@pytest.mark.django_db @override_settings(EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend") def test_create_message( api_client, @@ -355,6 +366,7 @@ def test_create_message( "handler-note-detail", ], ) +@pytest.mark.django_db def test_update_message_unauthenticated( anonymous_client, anonymous_handling_application, view_name ): @@ -380,6 +392,7 @@ def test_update_message_unauthenticated( ("handler-note-detail", MessageType.NOTE), ], ) +@pytest.mark.django_db def test_update_message_unauthorized( api_client, handler_api_client, @@ -415,6 +428,7 @@ def test_update_message_unauthorized( ) +@pytest.mark.django_db def test_update_message_not_allowed( api_client, handler_api_client, @@ -447,6 +461,7 @@ def test_update_message_not_allowed( "handler-note-detail", ], ) +@pytest.mark.django_db def test_delete_message_unauthenticated( anonymous_client, handling_application, view_name ): @@ -467,6 +482,7 @@ def test_delete_message_unauthenticated( "handler-message-detail", ], ) +@pytest.mark.django_db def test_delete_message_unauthorized( api_client, handler_api_client, @@ -502,6 +518,7 @@ def test_delete_message_unauthorized( assert result.status_code == 403 +@pytest.mark.django_db def test_delete_message( api_client, mock_get_organisation_roles_and_create_company, @@ -526,10 +543,13 @@ def test_delete_message( assert Message.objects.count() == 0 +@pytest.mark.django_db def test_applications_list_with_message_count( api_client, handling_application, handler_api_client ): - msg = MessageFactory(application=handling_application) + msg = MessageFactory( + application=handling_application, message_type=MessageType.HANDLER_MESSAGE + ) response = api_client.get(reverse("v1:applicant-application-list")) assert len(response.data) == 1 assert response.status_code == 200 @@ -563,6 +583,7 @@ def test_applications_list_with_message_count( assert response.data["unread_messages_count"] == 0 +@pytest.mark.django_db def test_list_messages_read_receipt_applicant( api_client, handling_application, @@ -658,6 +679,7 @@ def test_list_messages_read_receipt_handler( ) +@pytest.mark.django_db def test_applications_list_with_message_count_multiple_messages( api_client, handling_application, handler_api_client ): diff --git a/backend/benefit/messages/tests/test_models.py b/backend/benefit/messages/tests/test_models.py index 2756a9aeee..36b22210c7 100644 --- a/backend/benefit/messages/tests/test_models.py +++ b/backend/benefit/messages/tests/test_models.py @@ -1,7 +1,10 @@ +import pytest + from messages.models import Message from messages.tests.factories import MessageFactory +@pytest.mark.django_db def test_message_model(): msg = MessageFactory() assert Message.objects.count() == 1 diff --git a/backend/benefit/pyproject.toml b/backend/benefit/pyproject.toml index e9faee147f..d473019fe8 100644 --- a/backend/benefit/pyproject.toml +++ b/backend/benefit/pyproject.toml @@ -1,5 +1,5 @@ [tool.ruff] -target-version = "py39" +target-version = "py312" [tool.ruff.lint] select = [ @@ -23,3 +23,6 @@ extend-per-file-ignores = { "*/migrations/*" = ["E501"], "*/tests/*" = ["E501"] [tool.ruff.lint.isort] known-first-party = ["shared"] + +[tool.pip-tools] +strip-extras = true diff --git a/backend/benefit/requirements-dev.in b/backend/benefit/requirements-dev.in index 5cb45665b8..1a3547f587 100644 --- a/backend/benefit/requirements-dev.in +++ b/backend/benefit/requirements-dev.in @@ -1,9 +1,13 @@ +# NOTE: You need to comment out "-e file:../shared" in requirements.txt when running pip-compile. +# Otherwise, you might get an "Unnamed requirements are not allowed as constraints" error. +-c requirements.txt + ipython langdetect -pip-tools==7.4.1 +pip-tools pytest pytest-cov pytest-django -pytest-freezegun +pytest-freezer requests-mock werkzeug diff --git a/backend/benefit/requirements-dev.txt b/backend/benefit/requirements-dev.txt index c980394c91..8cba02d3d7 100644 --- a/backend/benefit/requirements-dev.txt +++ b/backend/benefit/requirements-dev.txt @@ -1,122 +1,124 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile requirements-dev.in +# pip-compile --strip-extras requirements-dev.in # -asttokens==2.2.1 +asttokens==3.0.0 # via stack-data -attrs==22.2.0 - # via pytest -backcall==0.2.0 - # via ipython -build==1.2.1 +build==1.3.0 # via pip-tools -certifi==2022.12.7 - # via requests -charset-normalizer==3.0.1 - # via requests -click==8.1.3 +certifi==2025.8.3 + # via + # -c requirements.txt + # requests +charset-normalizer==3.4.3 + # via + # -c requirements.txt + # requests +click==8.3.0 # via pip-tools -coverage[toml]==7.2.0 +coverage==7.10.7 # via pytest-cov -decorator==5.1.1 +decorator==5.2.1 # via ipython -exceptiongroup==1.1.0 - # via pytest -executing==1.2.0 +executing==2.2.1 # via stack-data -freezegun==1.2.2 - # via pytest-freezegun -idna==3.4 - # via requests -importlib-metadata==8.0.0 - # via build -iniconfig==2.0.0 +freezegun==1.5.5 + # via pytest-freezer +idna==3.10 + # via + # -c requirements.txt + # requests +iniconfig==2.1.0 # via pytest -ipython==8.10.0 +ipython==9.6.0 # via -r requirements-dev.in -jedi==0.18.2 +ipython-pygments-lexers==1.1.1 + # via ipython +jedi==0.19.2 # via ipython langdetect==1.0.9 # via -r requirements-dev.in -markupsafe==2.1.2 - # via werkzeug -matplotlib-inline==0.1.6 +markupsafe==3.0.2 + # via + # -c requirements.txt + # werkzeug +matplotlib-inline==0.1.7 # via ipython -packaging==23.0 +packaging==25.0 # via + # -c requirements.txt # build # pytest -parso==0.8.3 +parso==0.8.5 # via jedi -pexpect==4.8.0 - # via ipython -pickleshare==0.7.5 +pexpect==4.9.0 # via ipython -pip-tools==7.4.1 +pip-tools==7.5.1 # via -r requirements-dev.in -pluggy==1.0.0 - # via pytest -prompt-toolkit==3.0.37 +pluggy==1.6.0 + # via + # pytest + # pytest-cov +prompt-toolkit==3.0.52 # via ipython ptyprocess==0.7.0 # via pexpect -pure-eval==0.2.2 +pure-eval==0.2.3 # via stack-data -pygments==2.14.0 - # via ipython -pyproject-hooks==1.0.0 +pygments==2.19.2 + # via + # ipython + # ipython-pygments-lexers + # pytest +pyproject-hooks==1.2.0 # via # build # pip-tools -pytest==7.2.1 +pytest==8.4.2 # via # -r requirements-dev.in # pytest-cov # pytest-django - # pytest-freezegun -pytest-cov==4.0.0 + # pytest-freezer +pytest-cov==7.0.0 # via -r requirements-dev.in -pytest-django==4.5.2 +pytest-django==4.11.1 # via -r requirements-dev.in -pytest-freezegun==0.4.2 +pytest-freezer==0.4.9 # via -r requirements-dev.in -python-dateutil==2.8.2 - # via freezegun -requests==2.28.2 - # via requests-mock -requests-mock==1.10.0 +python-dateutil==2.9.0.post0 + # via + # -c requirements.txt + # freezegun +requests==2.32.5 + # via + # -c requirements.txt + # requests-mock +requests-mock==1.12.1 # via -r requirements-dev.in -six==1.16.0 +six==1.17.0 # via - # asttokens + # -c requirements.txt # langdetect # python-dateutil - # requests-mock -stack-data==0.6.2 +stack-data==0.6.3 # via ipython -tomli==2.0.1 - # via - # build - # coverage - # pip-tools - # pyproject-hooks - # pytest -traitlets==5.9.0 +traitlets==5.14.3 # via # ipython # matplotlib-inline -urllib3==1.26.14 - # via requests -wcwidth==0.2.6 +urllib3==2.5.0 + # via + # -c requirements.txt + # requests +wcwidth==0.2.14 # via prompt-toolkit -werkzeug==2.2.3 +werkzeug==3.1.3 # via -r requirements-dev.in -wheel==0.38.4 +wheel==0.45.1 # via pip-tools -zipp==3.19.2 - # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/backend/benefit/requirements-prod.in b/backend/benefit/requirements-prod.in index b4d5363447..caf986e879 100644 --- a/backend/benefit/requirements-prod.in +++ b/backend/benefit/requirements-prod.in @@ -1,2 +1 @@ --c requirements.txt uwsgi diff --git a/backend/benefit/requirements-prod.txt b/backend/benefit/requirements-prod.txt index b08f7ffc55..963579b8e6 100644 --- a/backend/benefit/requirements-prod.txt +++ b/backend/benefit/requirements-prod.txt @@ -1,8 +1,8 @@ # -# This file is autogenerated by pip-compile with python 3.9 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: # -# pip-compile requirements-prod.in +# pip-compile --strip-extras requirements-prod.in # -uwsgi==2.0.21 +uwsgi==2.0.30 # via -r requirements-prod.in diff --git a/backend/benefit/requirements.in b/backend/benefit/requirements.in index 0a495f33ab..6931f6db42 100644 --- a/backend/benefit/requirements.in +++ b/backend/benefit/requirements.in @@ -12,10 +12,10 @@ django-sql-utils django-storages[azure] django_auth_adfs djangorestframework -django~=4.2.25 +django~=5.2.6 drf-nested-routers drf-spectacular -elasticsearch~=7.17 # TODO: remove and update to 8.x when elasticsearch servers are updated to elasticsearch 8 +elasticsearch~=8.19 factory-boy # TODO: added temporarily so that NEXT_PUBLIC_MOCK_FLAG can be set on dev env, remove when authentication done filetype fuzzywuzzy @@ -23,7 +23,7 @@ helsinki-profile-gdpr-api jinja2 lxml mozilla_django_oidc -openpyxl==3.1.3 +openpyxl pandas pdfkit pillow diff --git a/backend/benefit/requirements.txt b/backend/benefit/requirements.txt index 2c9ce0a42a..4d96bd8e77 100644 --- a/backend/benefit/requirements.txt +++ b/backend/benefit/requirements.txt @@ -1,36 +1,46 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile requirements.in +# pip-compile --strip-extras requirements.in # -e file:../shared # via -r requirements.in -asgiref==3.6.0 - # via django -attrs==22.2.0 - # via jsonschema -azure-core==1.26.3 - # via azure-storage-blob -azure-storage-blob==12.15.0 +asgiref==3.9.1 + # via + # django + # django-cors-headers +attrs==25.3.0 + # via + # jsonschema + # referencing +authlib==1.6.4 + # via drf-oidc-auth +azure-core==1.35.1 + # via + # azure-storage-blob + # django-storages +azure-storage-blob==12.26.0 # via django-storages -babel==2.11.0 +babel==2.17.0 # via -r requirements.in -cachetools==5.3.0 +cachetools==6.2.0 # via django-helusers -certifi==2022.12.7 +certifi==2025.8.3 # via - # elasticsearch + # elastic-transport # requests # sentry-sdk -cffi==1.15.1 +cffi==2.0.0 # via cryptography -charset-normalizer==3.0.1 +charset-normalizer==3.4.3 # via requests -cryptography==39.0.1 +cryptography==43.0.3 # via + # authlib # azure-storage-blob # django-auth-adfs + # drf-oidc-auth # josepy # mozilla-django-oidc # pyopenssl @@ -41,7 +51,7 @@ defusedxml==0.7.1 # pysaml2 deprecation==2.1.0 # via django-helusers -django==4.2.25 +django==5.2.6 # via # -r requirements.in # django-auth-adfs @@ -52,6 +62,7 @@ django==4.2.25 # django-localflavor # django-phonenumber-field # django-searchable-encrypted-fields + # django-simple-history # django-sql-utils # django-storages # djangorestframework @@ -62,197 +73,196 @@ django==4.2.25 # helsinki-profile-gdpr-api # mozilla-django-oidc # yjdh-backend-shared -django-auth-adfs==1.11.4 +django-auth-adfs==1.15.0 # via # -r requirements.in # yjdh-backend-shared -django-cors-headers==3.13.0 +django-cors-headers==4.9.0 # via -r requirements.in -django-environ==0.9.0 +django-environ==0.12.0 # via -r requirements.in -django-extensions==3.2.1 +django-extensions==4.1 # via # -r requirements.in # yjdh-backend-shared -django-filter==22.1 +django-filter==25.1 # via -r requirements.in -django-helusers==0.11.0 +django-helusers==0.14.0 # via helsinki-profile-gdpr-api -django-localflavor==3.1 +django-localflavor==5.0 # via -r requirements.in -django-phonenumber-field[phonenumbers]==7.0.2 +django-phonenumber-field==8.1.0 # via -r requirements.in django-searchable-encrypted-fields==0.2.1 # via -r requirements.in -django-simple-history==3.4.0 +django-simple-history==3.10.1 # via -r requirements.in -django-sql-utils==0.6.1 +django-sql-utils==0.7.0 # via -r requirements.in -django-storages[azure]==1.13.2 +django-storages==1.14.6 # via -r requirements.in -djangorestframework==3.14.0 +djangorestframework==3.16.1 # via # -r requirements.in # drf-nested-routers # drf-oidc-auth # drf-spectacular # helsinki-profile-gdpr-api -djangosaml2==1.5.5 +djangosaml2==1.11.1 # via yjdh-backend-shared -drf-nested-routers==0.93.4 +drf-nested-routers==0.95.0 # via -r requirements.in -drf-oidc-auth==0.10.0 +drf-oidc-auth==3.0.0 # via helsinki-profile-gdpr-api -drf-spectacular==0.25.1 +drf-spectacular==0.28.0 # via -r requirements.in -ecdsa==0.18.0 +ecdsa==0.19.1 # via python-jose -elasticsearch==7.17.9 +elastic-transport==8.17.1 + # via elasticsearch +elasticsearch==8.19.1 # via -r requirements.in -elementpath==4.0.1 +elementpath==4.8.0 # via xmlschema -et-xmlfile==1.1.0 +et-xmlfile==2.0.0 # via openpyxl -factory-boy==3.2.1 +factory-boy==3.3.3 # via -r requirements.in -faker==17.0.0 +faker==37.8.0 # via factory-boy filetype==1.2.0 # via -r requirements.in -future==0.18.3 - # via pyjwkest fuzzywuzzy==0.18.0 # via -r requirements.in -helsinki-profile-gdpr-api==0.1.0 +helsinki-profile-gdpr-api==0.2.0 # via -r requirements.in -idna==3.4 +idna==3.10 # via requests inflection==0.5.1 # via drf-spectacular -isodate==0.6.1 +isodate==0.7.2 # via azure-storage-blob -jinja2==3.1.2 +jinja2==3.1.6 # via -r requirements.in -josepy==1.13.0 +josepy==2.1.0 # via mozilla-django-oidc -jsonschema==4.17.3 +jsonschema==4.25.1 # via drf-spectacular -levenshtein==0.25.1 +jsonschema-specifications==2025.9.1 + # via jsonschema +levenshtein==0.27.1 # via python-levenshtein -lxml==5.2.2 +lxml==6.0.2 # via -r requirements.in -markupsafe==2.1.2 +markupsafe==3.0.2 # via jinja2 -mozilla-django-oidc==3.0.0 +mozilla-django-oidc==4.0.1 # via # -r requirements.in # yjdh-backend-shared -numpy==1.26.4 +numpy==2.3.3 # via pandas -openpyxl==3.1.3 +openpyxl==3.1.5 # via -r requirements.in -packaging==23.0 +packaging==25.0 # via deprecation -pandas==2.2.2 +pandas==2.3.2 # via -r requirements.in pdfkit==1.0.0 # via -r requirements.in -phonenumbers==8.13.6 +phonenumbers==9.0.14 # via django-phonenumber-field -pillow==9.4.0 +pillow==11.3.0 # via -r requirements.in -psycopg2==2.9.5 +psycopg2==2.9.10 # via -r requirements.in -pyasn1==0.4.8 +pyasn1==0.6.1 # via # python-jose # rsa -pycparser==2.21 +pycparser==2.23 # via cffi -pycryptodome==3.17 +pycryptodome==3.23.0 # via django-searchable-encrypted-fields -pycryptodomex==3.17 - # via pyjwkest -pyjwkest==1.4.2 - # via drf-oidc-auth -pyjwt==2.6.0 +pyjwt==2.10.1 # via django-auth-adfs -pyopenssl==23.0.0 - # via - # josepy - # pysaml2 -pyrsistent==0.19.3 - # via jsonschema -pysaml2==7.4.1 +pyopenssl==24.2.1 + # via pysaml2 +pysaml2==7.5.2 # via djangosaml2 -python-dateutil==2.8.2 +python-dateutil==2.9.0.post0 # via # -r requirements.in - # faker + # elasticsearch # pandas # pysaml2 -python-jose==3.3.0 +python-jose==3.5.0 # via django-helusers -python-levenshtein==0.25.1 +python-levenshtein==0.27.1 # via -r requirements.in -python-stdnum==1.19 +python-stdnum==2.1 # via # -r requirements.in # django-localflavor -pytz==2022.7.1 +pytz==2025.2 # via - # babel - # djangorestframework # pandas # pysaml2 -pyyaml==6.0 +pyyaml==6.0.2 # via # -r requirements.in # drf-spectacular -rapidfuzz==3.9.0 +rapidfuzz==3.14.1 # via levenshtein -requests==2.28.2 +referencing==0.36.2 + # via + # jsonschema + # jsonschema-specifications +requests==2.32.5 # via # -r requirements.in # azure-core # django-auth-adfs # django-helusers + # drf-oidc-auth # mozilla-django-oidc - # pyjwkest # pysaml2 -rsa==4.9 +rpds-py==0.27.1 + # via + # jsonschema + # referencing +rsa==4.9.1 # via python-jose -sentry-sdk==1.15.0 +sentry-sdk==2.38.0 # via -r requirements.in -six==1.16.0 +six==1.17.0 # via # azure-core # ecdsa - # isodate - # pyjwkest # python-dateutil -sqlparse==0.4.3 +sqlparse==0.5.3 # via # django # django-sql-utils -typing-extensions==4.5.0 +typing-extensions==4.15.0 # via # azure-core # azure-storage-blob -tzdata==2024.1 - # via pandas -uritemplate==4.1.1 + # elasticsearch + # referencing +tzdata==2025.2 + # via + # faker + # pandas +uritemplate==4.2.0 # via # -r requirements.in # drf-spectacular -urllib3==1.26.14 +urllib3==2.5.0 # via # django-auth-adfs - # elasticsearch + # elastic-transport # requests # sentry-sdk -xmlschema==2.2.1 +xmlschema==2.5.1 # via pysaml2 - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/backend/benefit/terms/tests/factories.py b/backend/benefit/terms/tests/factories.py index c08ad2659d..08e83323c2 100644 --- a/backend/benefit/terms/tests/factories.py +++ b/backend/benefit/terms/tests/factories.py @@ -49,6 +49,7 @@ class TermsFactory(factory.django.DjangoModelFactory): class Meta: model = Terms + skip_postgeneration_save = True class AbstractTermsApprovalFactory(factory.django.DjangoModelFactory): @@ -72,6 +73,7 @@ def selected_applicant_consents(self, create, extracted, **kwargs): class Meta: model = AbstractTermsApproval abstract = True + skip_postgeneration_save = True class ApplicantTermsApprovalFactory(AbstractTermsApprovalFactory): diff --git a/backend/benefit/terms/tests/test_api.py b/backend/benefit/terms/tests/test_api.py index 005c35bf06..0133111c92 100644 --- a/backend/benefit/terms/tests/test_api.py +++ b/backend/benefit/terms/tests/test_api.py @@ -15,6 +15,7 @@ from terms.tests.factories import ApplicantTermsApprovalFactory, TermsFactory +@pytest.mark.django_db def test_applicant_terms_in_effect(api_client, application, accept_tos): """ Test that the API returns the correct Terms and ApplicantConsents in the applicant_terms_in_effect field. @@ -64,6 +65,7 @@ def test_applicant_terms_in_effect(api_client, application, accept_tos): ], ) @pytest.mark.parametrize("previously_approved", [False, True]) +@pytest.mark.django_db def test_approve_terms_success( api_client, request, @@ -127,6 +129,7 @@ def test_approve_terms_success( (ApplicationStatus.ADDITIONAL_INFORMATION_NEEDED, ApplicationStatus.HANDLING), ], ) +@pytest.mark.django_db def test_approve_wrong_terms(api_client, request, application, from_status, to_status): # current terms TermsFactory(effective_from=date.today(), terms_type=TermsType.APPLICANT_TERMS) @@ -168,6 +171,7 @@ def test_approve_wrong_terms(api_client, request, application, from_status, to_s (ApplicationStatus.ADDITIONAL_INFORMATION_NEEDED, ApplicationStatus.HANDLING), ], ) +@pytest.mark.django_db def test_approve_no_terms(api_client, request, application, from_status, to_status): # current terms TermsFactory(effective_from=date.today(), terms_type=TermsType.APPLICANT_TERMS) @@ -198,6 +202,7 @@ def test_approve_no_terms(api_client, request, application, from_status, to_stat ApplicationStatus.ADDITIONAL_INFORMATION_NEEDED, ], ) +@pytest.mark.django_db def test_approve_terms_missing_consent( api_client, request, application, applicant_terms, status ): @@ -234,6 +239,7 @@ def test_approve_terms_missing_consent( ApplicationStatus.ADDITIONAL_INFORMATION_NEEDED, ], ) +@pytest.mark.django_db def test_approve_terms_too_many_consents( api_client, request, application, applicant_terms, status ): @@ -274,6 +280,7 @@ def test_approve_terms_too_many_consents( ), ], ) +@pytest.mark.django_db def test_approve_terms_ignored_when_not_submitting_application( api_client, request, application, applicant_terms, from_status, to_status ): diff --git a/backend/benefit/terms/tests/test_models.py b/backend/benefit/terms/tests/test_models.py index f5bc4215f9..b8efdffbc1 100644 --- a/backend/benefit/terms/tests/test_models.py +++ b/backend/benefit/terms/tests/test_models.py @@ -11,12 +11,14 @@ from terms.tests.factories import TermsFactory +@pytest.mark.django_db def test_terms_model(applicant_terms): assert Terms.objects.count() == 1 assert applicant_terms.applicant_consents.count() == 2 assert applicant_terms.terms_pdf_fi is not None +@pytest.mark.django_db def test_terms_editable(applicant_terms): applicant_terms.effective_from = None assert applicant_terms.is_editable @@ -28,6 +30,7 @@ def test_terms_editable(applicant_terms): assert not applicant_terms.is_editable +@pytest.mark.django_db def test_applicant_terms_approval(applicant_terms_approval): assert applicant_terms_approval.terms is not None assert applicant_terms_approval.terms.applicant_consents.count() == 2 @@ -35,6 +38,7 @@ def test_applicant_terms_approval(applicant_terms_approval): assert applicant_terms_approval.terms is not None +@pytest.mark.django_db def test_terms_duplicate_effective_from(): # terms_type, effective_from are unique together TermsFactory(effective_from=date.today(), terms_type=TermsType.APPLICANT_TERMS) @@ -46,6 +50,7 @@ def test_terms_duplicate_effective_from(): TermsFactory(effective_from=date.today(), terms_type=TermsType.TERMS_OF_SERVICE) +@pytest.mark.django_db def test_current_terms(applicant_terms): # effective_from is None applicant_terms.effective_from = None diff --git a/backend/benefit/terms/tests/test_terms_of_service_api.py b/backend/benefit/terms/tests/test_terms_of_service_api.py index 26624676c5..c114cc4cc8 100644 --- a/backend/benefit/terms/tests/test_terms_of_service_api.py +++ b/backend/benefit/terms/tests/test_terms_of_service_api.py @@ -18,6 +18,7 @@ def get_current_user_url(): return "/v1/users/me/" +@pytest.mark.django_db def test_terms_of_service_in_effect_pdf( api_client, mock_get_organisation_roles_and_create_company ): @@ -60,6 +61,7 @@ def test_terms_of_service_in_effect_pdf( assert response.status_code == 200 +@pytest.mark.django_db def test_terms_of_service_in_effect_md( api_client, mock_get_organisation_roles_and_create_company ): @@ -112,6 +114,7 @@ def test_terms_of_service_in_effect_md( assert response.status_code == 200 +@pytest.mark.django_db @pytest.mark.parametrize("previously_approved", [False, True]) def test_approve_terms_success( api_client, @@ -155,6 +158,7 @@ def test_approve_terms_success( } +@pytest.mark.django_db def test_approve_wrong_terms( api_client, mock_get_organisation_roles_and_create_company ): @@ -182,6 +186,7 @@ def test_approve_wrong_terms( ) +@pytest.mark.django_db def test_approve_no_terms(api_client, mock_get_organisation_roles_and_create_company): # current terms TermsFactory(effective_from=date.today(), terms_type=TermsType.TERMS_OF_SERVICE) @@ -193,6 +198,7 @@ def test_approve_no_terms(api_client, mock_get_organisation_roles_and_create_com assert response.data.keys() == {"terms", "selected_applicant_consents"} +@pytest.mark.django_db def test_approve_terms_missing_consent( api_client, terms_of_service, mock_get_organisation_roles_and_create_company ): @@ -213,6 +219,7 @@ def test_approve_terms_missing_consent( ) +@pytest.mark.django_db def test_approve_terms_too_many_consents( api_client, terms_of_service, @@ -236,6 +243,7 @@ def test_approve_terms_too_many_consents( ) +@pytest.mark.django_db def test_validate_tos_approval_by_session( api_client, terms_of_service, diff --git a/backend/benefit/users/api/v1/views.py b/backend/benefit/users/api/v1/views.py index ea3d187591..27e33af674 100644 --- a/backend/benefit/users/api/v1/views.py +++ b/backend/benefit/users/api/v1/views.py @@ -1,19 +1,13 @@ from django.conf import settings from django.contrib.auth import get_user_model -from django.contrib.auth.base_user import AbstractBaseUser from django.core.exceptions import PermissionDenied -from django.db import DatabaseError, transaction from django.middleware.csrf import get_token -from django.shortcuts import get_object_or_404 from drf_spectacular.utils import extend_schema -from helsinki_gdpr.views import DeletionNotAllowed, DryRunException, GDPRAPIView from rest_framework import status from rest_framework.response import Response from rest_framework.views import APIView from common.permissions import BFIsAuthenticated -from users.api.v1.authentications import HelsinkiProfileApiTokenAuthentication -from users.api.v1.permissions import BFGDPRScopesPermission from users.api.v1.serializers import UserSerializer User = get_user_model() @@ -54,30 +48,3 @@ def get(self, request): return response return Response(status=status.HTTP_400_BAD_REQUEST) - - -class UserUuidGDPRAPIView(GDPRAPIView): - """GDPR API view that is used from Helsinki profile to query and delete user data.""" # noqa: E501 - - permission_classes = [BFGDPRScopesPermission] - authentication_classes = [HelsinkiProfileApiTokenAuthentication] - - def get_object(self) -> AbstractBaseUser: - """Get user by Helsinki-profile user ID that is stored as username.""" - obj = get_object_or_404(User, username=self.kwargs["uuid"]) - self.check_object_permissions(self.request, obj) - return obj - - def delete(self, *args, **kwargs): - """Delete all data related to the given user.""" - try: - with transaction.atomic(): - user = self.get_object() - user.delete() - self.check_dry_run() - except DryRunException: - pass - except DatabaseError: - raise DeletionNotAllowed() - - return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/backend/benefit/users/models.py b/backend/benefit/users/models.py index 2e7860e49b..82eb23cea2 100644 --- a/backend/benefit/users/models.py +++ b/backend/benefit/users/models.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import Optional from uuid import uuid4 from django.contrib.auth.models import AbstractUser @@ -7,7 +6,7 @@ from helsinki_gdpr.models import SerializableMixin -def format_date(date: datetime) -> Optional[str]: +def format_date(date: datetime) -> str | None: return date.strftime("%d-%m-%Y %H:%M:%S") if date else None diff --git a/backend/benefit/users/tests/test_gdpr_api.py b/backend/benefit/users/tests/test_gdpr_api.py index e06425e75f..55d1119a6b 100644 --- a/backend/benefit/users/tests/test_gdpr_api.py +++ b/backend/benefit/users/tests/test_gdpr_api.py @@ -4,6 +4,7 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.urls import reverse +from django.utils import timezone from helusers.settings import api_token_auth_settings from jose import jwt @@ -29,7 +30,7 @@ def get_api_token_for_user_with_scopes(user, scopes: list, requests_mock): keys = {"keys": [rsa_key.public_key_jwk]} - now = datetime.datetime.now() + now = timezone.now() expire = now + datetime.timedelta(days=14) jwt_data = { @@ -52,6 +53,7 @@ def get_api_token_for_user_with_scopes(user, scopes: list, requests_mock): return auth_header +@pytest.mark.django_db def test_get_profile_data_from_gdpr_api(gdpr_api_client, requests_mock): user = HelsinkiProfileUserFactory() auth_header = get_api_token_for_user_with_scopes( @@ -68,11 +70,14 @@ def test_get_profile_data_from_gdpr_api(gdpr_api_client, requests_mock): ], } gdpr_api_client.credentials(HTTP_AUTHORIZATION=auth_header) - response = gdpr_api_client.get(reverse("gdpr_v1", kwargs={"uuid": user.username})) + response = gdpr_api_client.get( + reverse("helsinki_gdpr:gdpr_v1", kwargs={"pk": user.username}) + ) assert response.status_code == 200 assert response.json() == valid_response +@pytest.mark.django_db def test_delete_profile_data_from_gdpr_api(gdpr_api_client, requests_mock): user = HelsinkiProfileUserFactory() auth_header = get_api_token_for_user_with_scopes( @@ -80,24 +85,28 @@ def test_delete_profile_data_from_gdpr_api(gdpr_api_client, requests_mock): ) gdpr_api_client.credentials(HTTP_AUTHORIZATION=auth_header) response = gdpr_api_client.delete( - reverse("gdpr_v1", kwargs={"uuid": user.username}) + reverse("helsinki_gdpr:gdpr_v1", kwargs={"pk": user.username}) ) assert response.status_code == 204 with pytest.raises(User.DoesNotExist): User.objects.get(username=user.username) +@pytest.mark.django_db def test_gdpr_api_requires_authentication(gdpr_api_client): user = HelsinkiProfileUserFactory() - response = gdpr_api_client.get(reverse("gdpr_v1", kwargs={"uuid": user.username})) + response = gdpr_api_client.get( + reverse("helsinki_gdpr:gdpr_v1", kwargs={"pk": user.username}) + ) assert response.status_code == 401 response = gdpr_api_client.delete( - reverse("gdpr_v1", kwargs={"uuid": user.username}) + reverse("helsinki_gdpr:gdpr_v1", kwargs={"pk": user.username}) ) assert response.status_code == 401 +@pytest.mark.django_db def test_user_can_only_access_their_own_profile(gdpr_api_client, requests_mock): user = HelsinkiProfileUserFactory() auth_header = get_api_token_for_user_with_scopes( @@ -109,11 +118,11 @@ def test_user_can_only_access_their_own_profile(gdpr_api_client, requests_mock): another_user = HelsinkiProfileUserFactory() response = gdpr_api_client.get( - reverse("gdpr_v1", kwargs={"uuid": another_user.username}) + reverse("helsinki_gdpr:gdpr_v1", kwargs={"pk": another_user.username}) ) assert response.status_code == 403 response = gdpr_api_client.delete( - reverse("gdpr_v1", kwargs={"uuid": another_user.username}) + reverse("helsinki_gdpr:gdpr_v1", kwargs={"pk": another_user.username}) ) assert response.status_code == 403 diff --git a/backend/benefit/users/tests/test_user_api.py b/backend/benefit/users/tests/test_user_api.py index 87e381f526..f19948d061 100644 --- a/backend/benefit/users/tests/test_user_api.py +++ b/backend/benefit/users/tests/test_user_api.py @@ -1,8 +1,10 @@ +import pytest from rest_framework.reverse import reverse from common.tests.conftest import get_client_user +@pytest.mark.django_db def test_applications_unauthorized(api_client, application): response = api_client.get(reverse("users-me")) user = get_client_user(api_client) diff --git a/backend/benefit/users/tests/test_user_model.py b/backend/benefit/users/tests/test_user_model.py index 39bd77f811..cbc478e0e3 100644 --- a/backend/benefit/users/tests/test_user_model.py +++ b/backend/benefit/users/tests/test_user_model.py @@ -1,7 +1,9 @@ +import pytest from django.contrib.auth import get_user_model User = get_user_model() +@pytest.mark.django_db def test_user_model(bf_user): assert User.objects.count() == 1 diff --git a/backend/docker/benefit-arm.Dockerfile b/backend/docker/benefit-arm.Dockerfile index dce0ec7a79..510f9d4d8d 100644 --- a/backend/docker/benefit-arm.Dockerfile +++ b/backend/docker/benefit-arm.Dockerfile @@ -1,5 +1,5 @@ # ============================== -FROM python:3.9-slim AS appbase +FROM python:3.12-slim AS appbase # ============================== WORKDIR / diff --git a/backend/docker/benefit-ubi-arm.Dockerfile b/backend/docker/benefit-ubi-arm.Dockerfile index 9724073cde..bebc40bdac 100644 --- a/backend/docker/benefit-ubi-arm.Dockerfile +++ b/backend/docker/benefit-ubi-arm.Dockerfile @@ -1,5 +1,5 @@ # ============================== -FROM registry.access.redhat.com/ubi9/python-39 AS appbase +FROM registry.access.redhat.com/ubi9/python-312 AS appbase # ============================== ARG SENTRY_RELEASE diff --git a/backend/docker/benefit.Dockerfile b/backend/docker/benefit.Dockerfile index b550799c3b..35aa97df81 100644 --- a/backend/docker/benefit.Dockerfile +++ b/backend/docker/benefit.Dockerfile @@ -1,5 +1,5 @@ # ============================== -FROM registry.access.redhat.com/ubi9/python-39 AS appbase +FROM registry.access.redhat.com/ubi9/python-312 AS appbase # ============================== ARG SENTRY_RELEASE diff --git a/backend/kesaseteli/docker-entrypoint.sh b/backend/kesaseteli/docker-entrypoint.sh index 7b0fedd063..54b592fc10 100755 --- a/backend/kesaseteli/docker-entrypoint.sh +++ b/backend/kesaseteli/docker-entrypoint.sh @@ -3,7 +3,7 @@ set -e # Wait for the database -if [ -z "$SKIP_DATABASE_CHECK" ] || [ "$SKIP_DATABASE_CHECK" = "0" ]; then +if [[ -z "$SKIP_DATABASE_CHECK" || "$SKIP_DATABASE_CHECK" = "0" ]]; then until nc -z -v -w30 "${DATABASE_HOST}" "${DATABASE_PORT-5432}" do echo "Waiting for postgres database connection..." diff --git a/backend/shared/shared/common/tests/factories.py b/backend/shared/shared/common/tests/factories.py index 8cbfc4f222..51493cd451 100644 --- a/backend/shared/shared/common/tests/factories.py +++ b/backend/shared/shared/common/tests/factories.py @@ -11,13 +11,7 @@ class UserFactory(factory.django.DjangoModelFactory): last_name = factory.Faker("last_name") email = factory.Faker("email") - username_base = factory.Faker("user_name") - username_suffix = factory.Faker( - "password", length=6, special_chars=False, upper_case=False - ) - username = factory.LazyAttribute( - lambda p: "{}_{}".format(p.username_base, p.username_suffix) - ) + username = factory.Faker("uuid4") class Meta: model = get_user_model()