From a0798746cd929ab339d67e09d42b62b02201a705 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Mon, 22 Sep 2025 13:57:03 +0300 Subject: [PATCH 01/29] build(benefit): upgrade to python 3.12 refs: HL-1625 --- .github/workflows/bf-py-coding-style.yml | 4 ++-- .github/workflows/bf-pytest.yml | 6 +++--- backend/docker/benefit-arm.Dockerfile | 2 +- backend/docker/benefit-ubi-arm.Dockerfile | 2 +- backend/docker/benefit.Dockerfile | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) 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/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 From 2636a9a41869a7874885da51b0a721e1b50dfa65 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Mon, 22 Sep 2025 14:55:14 +0300 Subject: [PATCH 02/29] refactor(benefit): apply pyupgrade refs: HL-1625 --- .../benefit/applications/benefit_aggregation.py | 13 ++++++------- backend/benefit/applications/models.py | 15 +++++++-------- backend/benefit/calculator/rules.py | 3 +-- backend/benefit/common/authentications.py | 2 +- backend/benefit/common/delay_call.py | 2 -- backend/benefit/common/localized_iban_field.py | 6 ++---- backend/benefit/common/utils.py | 14 +++++++------- backend/benefit/users/models.py | 3 +-- 8 files changed, 25 insertions(+), 33 deletions(-) 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/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/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/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/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 From 462ddde90a602e14dd4878b9d7e1c55054096c41 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 23 Sep 2025 11:16:47 +0300 Subject: [PATCH 03/29] deps(benefit): upgrade to Django 5.2 * add strip-extras to pip-tools * drop version pin from openpyxl * bump elasticsearch to 8 * upgrade all deps refs: HL-1625 --- backend/benefit/pyproject.toml | 5 +- backend/benefit/requirements-dev.in | 6 +- backend/benefit/requirements-dev.txt | 140 ++++++++--------- backend/benefit/requirements-prod.in | 1 - backend/benefit/requirements-prod.txt | 8 +- backend/benefit/requirements.in | 6 +- backend/benefit/requirements.txt | 212 ++++++++++++++------------ 7 files changed, 198 insertions(+), 180 deletions(-) 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..f01de27d31 100644 --- a/backend/benefit/requirements-dev.in +++ b/backend/benefit/requirements-dev.in @@ -1,6 +1,10 @@ +# 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 diff --git a/backend/benefit/requirements-dev.txt b/backend/benefit/requirements-dev.txt index c980394c91..7bf90498f9 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 --cert=None --client-cert=None --index-url=None --pip-args=None --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 +freezegun==1.5.5 # via pytest-freezegun -idna==3.4 - # via requests -importlib-metadata==8.0.0 - # via build -iniconfig==2.0.0 +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-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 # 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 From 98333eac2572d5dc0812b77679b59f034e80eef9 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Mon, 13 Oct 2025 11:14:40 +0300 Subject: [PATCH 04/29] fix: update SENTRY variable names WITH_LOCALS -> INCLUDE_LOCAL_VARIABLES REQUEST_BODIES -> MAX_REQUEST_BODY_SIZE refs: HL-1625 --- backend/benefit/helsinkibenefit/settings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/benefit/helsinkibenefit/settings.py b/backend/benefit/helsinkibenefit/settings.py index 203d3583e1..62c59beb17 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"), From 264f4b8a16a541c9d11e3df729e1dd9d4a4e830b Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Mon, 13 Oct 2025 12:04:27 +0300 Subject: [PATCH 05/29] test(benefit): fix pytest_sessionfinish crash if test collection errors In case of test collection errors, the pytest_sessionfinish fixture would also raise an error as MEDIA_ROOT does not exist before any tests are run. This in turn would hide the actual test collection errors from test output. refs: HL-1625 --- backend/benefit/applications/tests/conftest.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/benefit/applications/tests/conftest.py b/backend/benefit/applications/tests/conftest.py index 7228c779a0..cbf833058d 100755 --- a/backend/benefit/applications/tests/conftest.py +++ b/backend/benefit/applications/tests/conftest.py @@ -514,15 +514,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 From b4f6f7dcfc2a46bd108394b39063bd07a25f5ca4 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Mon, 13 Oct 2025 16:44:20 +0300 Subject: [PATCH 06/29] fix: simplify GDPR API And make it compatible with helsinki-profile-gdpr-api 0.2.0 --- backend/benefit/helsinkibenefit/gdpr.py | 17 ++++++++++ backend/benefit/helsinkibenefit/settings.py | 3 ++ backend/benefit/helsinkibenefit/urls.py | 4 +-- backend/benefit/users/api/v1/views.py | 33 -------------------- backend/benefit/users/tests/test_gdpr_api.py | 16 ++++++---- 5 files changed, 32 insertions(+), 41 deletions(-) create mode 100644 backend/benefit/helsinkibenefit/gdpr.py 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 62c59beb17..34b530a511 100644 --- a/backend/benefit/helsinkibenefit/settings.py +++ b/backend/benefit/helsinkibenefit/settings.py @@ -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/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/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/tests/test_gdpr_api.py b/backend/benefit/users/tests/test_gdpr_api.py index e06425e75f..4998afeca8 100644 --- a/backend/benefit/users/tests/test_gdpr_api.py +++ b/backend/benefit/users/tests/test_gdpr_api.py @@ -68,7 +68,9 @@ 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 @@ -80,7 +82,7 @@ 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): @@ -89,11 +91,13 @@ def test_delete_profile_data_from_gdpr_api(gdpr_api_client, requests_mock): 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 @@ -109,11 +113,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 From f2816008bd249e1313f9d859f8bc186ea83fcb17 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Mon, 13 Oct 2025 16:54:10 +0300 Subject: [PATCH 07/29] test(benefit): update test compatibility with Django 5.2 Also clean up and get rid of seemingly redundant star imports. refs: HL-1625 --- .../benefit/applications/tests/conftest.py | 4 ++-- .../tests/test_applications_api.py | 21 +++++++------------ .../tests/test_applications_report.py | 5 ----- .../tests/test_talpa_integration.py | 18 +++++++--------- 4 files changed, 18 insertions(+), 30 deletions(-) mode change 100755 => 100644 backend/benefit/applications/tests/conftest.py diff --git a/backend/benefit/applications/tests/conftest.py b/backend/benefit/applications/tests/conftest.py old mode 100755 new mode 100644 index cbf833058d..2cc831819b --- a/backend/benefit/applications/tests/conftest.py +++ b/backend/benefit/applications/tests/conftest.py @@ -1,7 +1,7 @@ import os import random import uuid -from datetime import date, datetime, timedelta +from datetime import date, timedelta import factory import pytest @@ -1293,5 +1293,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/test_applications_api.py b/backend/benefit/applications/tests/test_applications_api.py index 01f5ce9ede..6eb14da736 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,7 +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) @@ -507,7 +504,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()} == { @@ -1286,7 +1283,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 @@ -1397,9 +1394,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 @@ -1570,7 +1565,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( @@ -2230,7 +2225,7 @@ 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 ) @@ -2462,7 +2457,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_report.py b/backend/benefit/applications/tests/test_applications_report.py index 4cbf4a7366..e452d6f900 100644 --- a/backend/benefit/applications/tests/test_applications_report.py +++ b/backend/benefit/applications/tests/test_applications_report.py @@ -29,16 +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) diff --git a/backend/benefit/applications/tests/test_talpa_integration.py b/backend/benefit/applications/tests/test_talpa_integration.py index 93a00fbdd1..6efccb7fde 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,12 +15,9 @@ 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 @@ -71,7 +69,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) @@ -161,7 +159,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") @@ -177,7 +175,7 @@ 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( @@ -250,7 +248,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 +366,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) @@ -441,7 +439,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 From b0ee5228ac6dcace82dc0d530b71bafd5a6a0afd Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Mon, 13 Oct 2025 23:19:54 +0300 Subject: [PATCH 08/29] test: generate uuid4 usernames in UserFactory Default to uuid4 usernames as it represents the real state of things. refs: HL-1625 --- .../benefit/applications/tests/test_ahjo_integration.py | 6 ------ backend/shared/shared/common/tests/factories.py | 8 +------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/backend/benefit/applications/tests/test_ahjo_integration.py b/backend/benefit/applications/tests/test_ahjo_integration.py index 69b2a33a89..1e4733a28f 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 @@ -239,9 +238,7 @@ def test_generate_composed_template_html(mock_pdf_convert): ) -# Test flaking if no reseed is used def test_export_application_batch(application_batch): - reseed(12345) application_batch.applications.add( DecidedApplicationFactory.create( status=ApplicationStatus.ACCEPTED, @@ -249,12 +246,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,7 +263,6 @@ def test_export_application_batch(application_batch): ).count() + 4 ) - reseed(777) @patch("applications.services.ahjo_integration.pdfkit.from_string") 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() From f608f5bb25af381019f24fc09fa1f57115cf3e30 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 14 Oct 2025 14:54:18 +0300 Subject: [PATCH 09/29] test(benefit): remove seeding and remove autouse db This creates so many problems. Database state persists over tests causing flaky tests when autouse db is in use. This creates interdependencies between tests and test ordering. Same thing with seeding, it merely hides flakiness from tests instead of fixing the root cause. refs: HL-1625 --- .../benefit/applications/tests/conftest.py | 14 --- .../tests/test_ahjo_authentication.py | 6 ++ .../test_ahjo_decision_proposal_drafts.py | 1 + .../applications/tests/test_ahjo_decisions.py | 8 ++ .../tests/test_ahjo_integration.py | 26 ++++-- .../applications/tests/test_ahjo_payload.py | 13 +++ .../applications/tests/test_ahjo_requests.py | 3 + .../tests/test_ahjo_response_handler.py | 2 + .../applications/tests/test_ahjo_settings.py | 1 + .../tests/test_ahjo_xml_builder.py | 8 ++ .../applications/tests/test_alteration_api.py | 20 +++++ .../tests/test_application_batch_api.py | 14 ++- .../tests/test_application_change_sets.py | 2 + .../tests/test_application_clone.py | 2 + .../tests/test_application_tasks.py | 4 + .../tests/test_applications_api.py | 89 +++++++++++++++++-- .../test_applications_api_breaking_changes.py | 4 + .../tests/test_applications_report.py | 25 ++++-- ...st_command_import_archival_applications.py | 2 + .../tests/test_command_set_as_archived.py | 2 + .../benefit/applications/tests/test_models.py | 8 ++ .../tests/test_power_bi_integration.py | 2 + .../applications/tests/test_serializers.py | 2 + .../tests/test_talpa_integration.py | 11 +++ .../applications/tests/test_view_sets.py | 1 + .../calculator/tests/test_calculator_api.py | 3 + .../tests/test_handler_excel_examples.py | 2 + .../calculator/tests/test_manual_override.py | 1 + .../benefit/calculator/tests/test_models.py | 9 ++ .../tests/test_previous_benefits_api.py | 17 ++++ backend/benefit/common/tests/conftest.py | 4 - .../benefit/helsinkibenefit/tests/conftest.py | 10 --- backend/benefit/messages/tests/test_api.py | 17 ++++ backend/benefit/messages/tests/test_models.py | 3 + backend/benefit/terms/tests/test_api.py | 7 ++ backend/benefit/terms/tests/test_models.py | 5 ++ .../terms/tests/test_terms_of_service_api.py | 8 ++ backend/benefit/users/tests/test_gdpr_api.py | 4 + backend/benefit/users/tests/test_user_api.py | 2 + .../benefit/users/tests/test_user_model.py | 2 + 40 files changed, 304 insertions(+), 60 deletions(-) diff --git a/backend/benefit/applications/tests/conftest.py b/backend/benefit/applications/tests/conftest.py index 2cc831819b..4b7a8064e3 100644 --- a/backend/benefit/applications/tests/conftest.py +++ b/backend/benefit/applications/tests/conftest.py @@ -6,7 +6,6 @@ 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 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 1e4733a28f..84c97ae661 100644 --- a/backend/benefit/applications/tests/test_ahjo_integration.py +++ b/backend/benefit/applications/tests/test_ahjo_integration.py @@ -65,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 @@ -101,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, @@ -143,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 = {} @@ -173,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 = {} @@ -238,6 +233,7 @@ def test_generate_composed_template_html(mock_pdf_convert): ) +@pytest.mark.django_db def test_export_application_batch(application_batch): application_batch.applications.add( DecidedApplicationFactory.create( @@ -265,6 +261,7 @@ def test_export_application_batch(application_batch): ) +@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 = {} @@ -323,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) @@ -342,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}) @@ -359,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() @@ -370,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 ): @@ -387,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 ): @@ -546,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, @@ -674,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 ): @@ -700,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 ): @@ -714,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 ): @@ -990,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 ): @@ -1206,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, @@ -1595,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, @@ -1622,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..5de335d780 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) @@ -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) @@ -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..8e44f3c07b 100755 --- a/backend/benefit/applications/tests/test_application_batch_api.py +++ b/backend/benefit/applications/tests/test_application_batch_api.py @@ -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 ( @@ -821,6 +817,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 +854,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..8a4f3c99bf 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,7 @@ def check_applicant_changes(applicant_edit_payloads, changes, application): assert len(changes) == len(applicant_edit_payloads) +@pytest.mark.django_db def test_application_history_change_sets( request, handler_api_client, api_client, application ): 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_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 6eb14da736..c63073205b 100755 --- a/backend/benefit/applications/tests/test_applications_api.py +++ b/backend/benefit/applications/tests/test_applications_api.py @@ -64,14 +64,6 @@ from terms.models import TermsOfServiceApproval -@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): return reverse("v1:applicant-application-detail", kwargs={"pk": application.id}) @@ -89,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 @@ -108,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, @@ -119,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() @@ -144,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 ): @@ -154,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 @@ -169,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") @@ -180,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") @@ -191,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 ): @@ -207,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") @@ -220,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") @@ -244,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 ): @@ -254,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 ): @@ -268,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 ): @@ -331,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 ): @@ -338,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 ): @@ -360,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 ): @@ -394,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 ): @@ -427,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 ): @@ -440,6 +451,7 @@ def test_application_single_read_as_handler( assert "batch" in response.data +@pytest.mark.django_db def test_application_submitted_at( api_client, application, received_application, handling_application ): @@ -451,6 +463,7 @@ def test_application_submitted_at( assert response.data["submitted_at"].isoformat() == "2021-06-04T00:00:00+00:00" +@pytest.mark.django_db def test_application_template(api_client): response = api_client.get( reverse("v1:applicant-application-get-application-template") @@ -460,6 +473,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() @@ -480,6 +494,7 @@ def test_application_post_success_unauthenticated(anonymous_client, application) assert audit_event["target"]["type"] == "Application" +@pytest.mark.django_db def test_application_post_success(api_client, application): """ Create a new application @@ -533,6 +548,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 @@ -594,6 +610,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 ): @@ -634,6 +651,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() @@ -671,6 +689,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" @@ -681,6 +700,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 ): @@ -699,6 +719,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 @@ -723,6 +744,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 @@ -741,6 +763,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 @@ -772,6 +795,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 @@ -795,6 +819,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 @@ -819,6 +844,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 @@ -839,6 +865,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 @@ -853,6 +880,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 @@ -872,6 +900,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 @@ -890,6 +919,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 @@ -908,6 +938,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 ): @@ -936,6 +967,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 ): @@ -957,6 +989,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 ): @@ -974,6 +1007,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 ): @@ -990,6 +1024,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 @@ -1040,6 +1075,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 ): @@ -1084,6 +1120,7 @@ def test_application_date_range( ), # start_date in next year (relative to freeze_time date) ], ) +@pytest.mark.django_db def test_application_date_range_on_submit( request, api_client, application, start_date, end_date, status_code ): @@ -1108,6 +1145,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 ): @@ -1137,6 +1175,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, @@ -1189,6 +1228,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, @@ -1247,6 +1287,7 @@ def test_apprenticeship_program_validation_on_submit( (ApplicationStatus.REJECTED, ApplicationStatus.DRAFT, 400), ], ) +@pytest.mark.django_db def test_application_status_change_as_applicant( request, api_client, application, from_status, to_status, expected_code ): @@ -1321,6 +1362,7 @@ def test_application_status_change_as_applicant( ], ) @pytest.mark.parametrize("log_entry_comment", [None, "", "comment"]) +@pytest.mark.django_db @override_settings(EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend") def test_application_status_change_as_handler( request, @@ -1402,6 +1444,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, @@ -1444,6 +1487,7 @@ def test_application_accept( ("KISSA123", True, True), ], ) +@pytest.mark.django_db def test_application_with_batch_back_to_handling( request, handler_api_client, @@ -1494,6 +1538,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 ): @@ -1558,6 +1603,7 @@ def add_attachments_to_application(request, application, is_handler_application= _add_pdf_attachment(request, application, AttachmentType.FULL_APPLICATION) +@pytest.mark.django_db def test_application_modified_at_draft(api_client, application): """ DRAFT application's last_modified_at is visible to applicant @@ -1578,6 +1624,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 @@ -1600,6 +1647,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, @@ -1622,6 +1670,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 @@ -1649,6 +1698,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)) @@ -1777,6 +1827,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() @@ -1806,6 +1857,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 ): @@ -1842,6 +1894,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 ): @@ -1906,6 +1959,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() @@ -1915,6 +1969,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}") @@ -1936,6 +1991,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): @@ -1950,6 +2006,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 ): @@ -1980,6 +2037,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 @@ -2022,6 +2080,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 @@ -2053,6 +2112,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 @@ -2121,6 +2181,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 @@ -2142,6 +2203,7 @@ def test_application_number(api_client, application): assert next_application.application_number == application.application_number + 2 +@pytest.mark.django_db def test_application_api_before_accept_tos(api_client, application): # Clear user TOS approval TermsOfServiceApproval.objects.all().delete() @@ -2197,6 +2259,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 @@ -2215,6 +2278,7 @@ 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 def test_application_status_last_changed_at(api_client, handling_application): with freeze_time("2021-12-01"): ApplicationLogEntry.objects.create( @@ -2229,6 +2293,7 @@ def test_application_status_last_changed_at(api_client, handling_application): ) +@pytest.mark.django_db def test_handler_application_default_ordering(handler_api_client): _create_random_applications() @@ -2263,6 +2328,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" @@ -2298,6 +2364,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()] @@ -2318,6 +2385,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(), @@ -2342,6 +2410,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 @@ -2352,6 +2421,7 @@ def test_application_pdf_print(api_client, application): assert response.status_code == 200 +@pytest.mark.django_db def test_application_pdf_print_denied(api_client, anonymous_client): settings.NEXT_PUBLIC_MOCK_FLAG = False # noqa @@ -2371,6 +2441,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 @@ -2398,6 +2469,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"), @@ -2423,6 +2495,7 @@ def test_applications_with_unread_messages(api_client, handler_api_client, appli assert len(response.data) == 0 +@pytest.mark.django_db def test_require_additional_information(handler_api_client, application, mailoutbox): application.status = ApplicationStatus.HANDLING application.save() 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 e452d6f900..0d02e662b2 100644 --- a/backend/benefit/applications/tests/test_applications_report.py +++ b/backend/benefit/applications/tests/test_applications_report.py @@ -36,14 +36,6 @@ from calculator.tests.factories import PaySubsidyFactory -@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( archive: ZipFile, ) -> Dict[str, List[str]]: @@ -134,6 +126,7 @@ def _create_applications_for_export(): return (application1, application2, application3, application4) +@pytest.mark.django_db @pytest.mark.skip( reason=( "This test fails in deploy pipeline - DETAIL: Key" @@ -208,6 +201,7 @@ def test_applications_csv_export_new_applications(handler_api_client): assert ApplicationBatch.objects.all().count() == 2 +@pytest.mark.django_db def test_application_alteration_csv_export( application_alteration, handler_api_client, decided_application ): @@ -350,6 +344,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() @@ -366,6 +361,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() @@ -472,6 +468,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 ): @@ -584,6 +581,7 @@ def test_application_alteration_csv_output( ) +@pytest.mark.django_db def test_write_application_alterations_csv_file( application_alteration_csv_service, tmp_path ): @@ -603,6 +601,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 ): @@ -677,6 +676,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"' @@ -780,18 +780,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 ): @@ -912,6 +915,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, ): @@ -924,6 +928,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, ): @@ -967,6 +972,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, ): @@ -980,6 +986,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" @@ -999,6 +1006,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, ): @@ -1025,6 +1033,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..8743926a64 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() 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..5b54b069fa 100644 --- a/backend/benefit/applications/tests/test_serializers.py +++ b/backend/benefit/applications/tests/test_serializers.py @@ -12,6 +12,7 @@ "application_serializer", [ApplicantApplicationSerializer, HandlerApplicationSerializer], ) +@pytest.mark.django_db def test_logged_in_user_is_admin_with_anonymous_user( settings, anonymous_client, @@ -27,6 +28,7 @@ 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, diff --git a/backend/benefit/applications/tests/test_talpa_integration.py b/backend/benefit/applications/tests/test_talpa_integration.py index 6efccb7fde..5634510984 100644 --- a/backend/benefit/applications/tests/test_talpa_integration.py +++ b/backend/benefit/applications/tests/test_talpa_integration.py @@ -21,6 +21,7 @@ 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 @@ -34,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 @@ -54,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, @@ -109,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, ): @@ -123,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() @@ -142,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, @@ -171,6 +178,7 @@ 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() @@ -184,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 ): @@ -200,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, @@ -426,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 ): 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/tests/test_calculator_api.py b/backend/benefit/calculator/tests/test_calculator_api.py index bd9366e0c1..19caaab9ed 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,7 @@ def test_application_calculation_rows_id_exists( "number_of_instalments, has_subsidies", [(2, False), (1, True)], ) +@pytest.mark.django_db 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/tests/conftest.py b/backend/benefit/common/tests/conftest.py index 5957b12dce..bd4d39a845 100644 --- a/backend/benefit/common/tests/conftest.py +++ b/backend/benefit/common/tests/conftest.py @@ -6,7 +6,6 @@ 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,9 +24,6 @@ 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 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/messages/tests/test_api.py b/backend/benefit/messages/tests/test_api.py index adf78bc03c..7c48d39730 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,6 +54,7 @@ def test_list_message_unauthenticated( "handler-note-list", ], ) +@pytest.mark.django_db def test_list_message_unauthorized( api_client, anonymous_handling_application, view_name ): @@ -81,6 +83,7 @@ def test_list_message_unauthorized( "handler-message-list", ], ) +@pytest.mark.django_db def test_list_messages( api_client, handler_api_client, @@ -144,6 +147,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 +166,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 +183,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 +256,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 +290,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 +363,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 +389,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 +425,7 @@ def test_update_message_unauthorized( ) +@pytest.mark.django_db def test_update_message_not_allowed( api_client, handler_api_client, @@ -447,6 +458,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 +479,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 +515,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,6 +540,7 @@ 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 ): @@ -563,6 +578,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 +674,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/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/tests/test_gdpr_api.py b/backend/benefit/users/tests/test_gdpr_api.py index 4998afeca8..1380c9ca12 100644 --- a/backend/benefit/users/tests/test_gdpr_api.py +++ b/backend/benefit/users/tests/test_gdpr_api.py @@ -52,6 +52,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( @@ -75,6 +76,7 @@ def test_get_profile_data_from_gdpr_api(gdpr_api_client, requests_mock): 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( @@ -89,6 +91,7 @@ def test_delete_profile_data_from_gdpr_api(gdpr_api_client, requests_mock): 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( @@ -102,6 +105,7 @@ def test_gdpr_api_requires_authentication(gdpr_api_client): 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( 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 From a1a1a8e831052909c09bda5c6ac896afb8ff31d8 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 14 Oct 2025 15:01:27 +0300 Subject: [PATCH 10/29] test(benefit): flaky by freezing time Original test could generate de minimis granted at values that would cause validation errors during the test as they would be too old (over 4 years). Resolving by freezing time near the other dates used in the test. refs: HL-1625 --- .../benefit/applications/tests/test_application_change_sets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/benefit/applications/tests/test_application_change_sets.py b/backend/benefit/applications/tests/test_application_change_sets.py index 8a4f3c99bf..a002f251ba 100755 --- a/backend/benefit/applications/tests/test_application_change_sets.py +++ b/backend/benefit/applications/tests/test_application_change_sets.py @@ -187,6 +187,8 @@ def check_applicant_changes(applicant_edit_payloads, changes, application): @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 ): @@ -200,7 +202,6 @@ def test_application_history_change_sets( get_handler_detail_url(application), payload, ) - assert response.status_code == 200 application.refresh_from_db() From 8fffa7761ad85d0889f2c5e12d2a8f18ccc4e5e3 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 14 Oct 2025 15:05:04 +0300 Subject: [PATCH 11/29] test(benefit): insufficiently mocked tests These tests were originally relying on user session state from previously run test. With proper test isolation and without mocking they would then attempt to get the roles from suomi.fi instead and failing with 403. refs: HL-1625 --- .../benefit/applications/tests/test_applications_api.py | 8 ++++++-- backend/benefit/messages/tests/test_api.py | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/backend/benefit/applications/tests/test_applications_api.py b/backend/benefit/applications/tests/test_applications_api.py index c63073205b..e884831690 100755 --- a/backend/benefit/applications/tests/test_applications_api.py +++ b/backend/benefit/applications/tests/test_applications_api.py @@ -464,7 +464,9 @@ def test_application_submitted_at( @pytest.mark.django_db -def test_application_template(api_client): +def test_application_template( + api_client, mock_get_organisation_roles_and_create_company +): response = api_client.get( reverse("v1:applicant-application-get-application-template") ) @@ -2422,7 +2424,9 @@ def test_application_pdf_print(api_client, application): @pytest.mark.django_db -def test_application_pdf_print_denied(api_client, anonymous_client): +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() diff --git a/backend/benefit/messages/tests/test_api.py b/backend/benefit/messages/tests/test_api.py index 7c48d39730..9427d536ee 100644 --- a/backend/benefit/messages/tests/test_api.py +++ b/backend/benefit/messages/tests/test_api.py @@ -56,7 +56,10 @@ def test_list_message_unauthenticated( ) @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 From 607b96b98b5f6fe8f99bfdddc1dbc4e82b59a085 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 14 Oct 2025 15:06:07 +0300 Subject: [PATCH 12/29] test(benefit): lazy use of autouse This test probably relied on some chain of autouse to create terms of service without directly depending on it. Fixed by adding direct dependency to terms of service. refs: HL-1625 --- backend/benefit/applications/tests/test_applications_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/benefit/applications/tests/test_applications_api.py b/backend/benefit/applications/tests/test_applications_api.py index e884831690..e1c0f7fb8a 100755 --- a/backend/benefit/applications/tests/test_applications_api.py +++ b/backend/benefit/applications/tests/test_applications_api.py @@ -2206,7 +2206,7 @@ def test_application_number(api_client, application): @pytest.mark.django_db -def test_application_api_before_accept_tos(api_client, application): +def test_application_api_before_accept_tos(api_client, application, terms_of_service): # Clear user TOS approval TermsOfServiceApproval.objects.all().delete() From 50a4fdefd2499e72722526d8115a3ba9dffa265f Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 14 Oct 2025 15:07:05 +0300 Subject: [PATCH 13/29] test(benefit): flaky test due to random application language Test depends on stable language, so setting it to English. refs: HL-1625 --- backend/benefit/applications/tests/test_applications_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/benefit/applications/tests/test_applications_api.py b/backend/benefit/applications/tests/test_applications_api.py index e1c0f7fb8a..4b2e0a1866 100755 --- a/backend/benefit/applications/tests/test_applications_api.py +++ b/backend/benefit/applications/tests/test_applications_api.py @@ -2502,6 +2502,7 @@ def test_applications_with_unread_messages(api_client, handler_api_client, appli @pytest.mark.django_db 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( From 4fa904504e145d656cc9c3cb931bda4838890a15 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 14 Oct 2025 15:26:44 +0300 Subject: [PATCH 14/29] test(benefit): flaky test by setting message_type This test would fail 33% of the time if message_type was randomized to NOTE which is excluded from the unread message count. refs: HL-1625 --- backend/benefit/messages/tests/test_api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/benefit/messages/tests/test_api.py b/backend/benefit/messages/tests/test_api.py index 9427d536ee..9110db689a 100644 --- a/backend/benefit/messages/tests/test_api.py +++ b/backend/benefit/messages/tests/test_api.py @@ -547,7 +547,9 @@ def test_delete_message( 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 From 1c0926f8ccd6c32b57febfb9f3717e4eb1af73e8 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 14 Oct 2025 15:34:34 +0300 Subject: [PATCH 15/29] test(benefit): incorrect assert due to previous autouse This test asserted that there were 3 companies although the import data contained only two. 1 company was created automatically through autouse fixtures. refs: HL-1625 --- .../tests/test_command_import_archival_applications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8743926a64..85d98b63cf 100644 --- a/backend/benefit/applications/tests/test_command_import_archival_applications.py +++ b/backend/benefit/applications/tests/test_command_import_archival_applications.py @@ -152,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() From d5b2cbb62c704cca6533b7d51cd5f7d75f62de2b Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 14 Oct 2025 15:41:53 +0300 Subject: [PATCH 16/29] test(benefit): test dependency on company This test depended on a company to exist. Previously it was created by other tests. refs: HL-1625 --- backend/benefit/applications/tests/test_serializers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/benefit/applications/tests/test_serializers.py b/backend/benefit/applications/tests/test_serializers.py index 5b54b069fa..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]) @@ -35,6 +36,7 @@ def test_get_logged_in_user_company_with_anonymous_user( 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 From 5b0780def4bd42f795bd5abe7fbf75b2a04467b6 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 14 Oct 2025 15:52:13 +0300 Subject: [PATCH 17/29] test(benefit): unfreeze time This breaks a bunch of tests which will be fixed separately. refs: HL-1625 --- backend/benefit/common/tests/conftest.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/benefit/common/tests/conftest.py b/backend/benefit/common/tests/conftest.py index bd4d39a845..66774fc191 100644 --- a/backend/benefit/common/tests/conftest.py +++ b/backend/benefit/common/tests/conftest.py @@ -5,7 +5,6 @@ import pytest from django.contrib.auth.models import Permission from django.utils.translation import activate -from freezegun import freeze_time from rest_framework.authtoken.models import Token from rest_framework.test import APIClient @@ -25,8 +24,6 @@ def setup_test_environment(settings): settings.DISABLE_TOS_APPROVAL_CHECK = False settings.NEXT_PUBLIC_MOCK_FLAG = False activate("en") - with freeze_time("2021-06-04"): - yield @pytest.fixture From 9067e098a0524eaf9dc2db78097a352b26705df1 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 14 Oct 2025 15:54:07 +0300 Subject: [PATCH 18/29] test(benefit): remove milliseconds from test data This test would previously pass because time was frozen in a way that would round milliseconds away automatically. The production code removes milliseconds however, so the test helper function has been modified to reflect this. refs: HL-1625 --- backend/benefit/applications/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/benefit/applications/tests/conftest.py b/backend/benefit/applications/tests/conftest.py index 4b7a8064e3..e636d64ff3 100644 --- a/backend/benefit/applications/tests/conftest.py +++ b/backend/benefit/applications/tests/conftest.py @@ -421,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, From e6711cab7d5543559625c8849b1da58c38e37299 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 14 Oct 2025 16:06:32 +0300 Subject: [PATCH 19/29] test: another isoformat fix --- backend/benefit/applications/tests/test_ahjo_payload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/benefit/applications/tests/test_ahjo_payload.py b/backend/benefit/applications/tests/test_ahjo_payload.py index 5de335d780..c7cc2ed349 100644 --- a/backend/benefit/applications/tests/test_ahjo_payload.py +++ b/backend/benefit/applications/tests/test_ahjo_payload.py @@ -430,7 +430,7 @@ def test_prepare_update_application_payload(decided_application): { "Title": f"{title}", "Type": AhjoRecordType.APPLICATION, - "Acquired": application.submitted_at.isoformat(), + "Acquired": application.submitted_at.isoformat("T", "seconds"), "PublicityClass": "Salassa pidettävä", "SecurityReasons": ["JulkL (621/1999) 24.1 § 25 k"], "Language": "fi", From 3090c7a60b4fa4aa731eb7cd9452d42efefcf7cc Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 14 Oct 2025 16:07:47 +0300 Subject: [PATCH 20/29] test(benefit): remove internal assumption about external time --- .../applications/tests/test_application_batch_api.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/benefit/applications/tests/test_application_batch_api.py b/backend/benefit/applications/tests/test_application_batch_api.py index 8e44f3c07b..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 @@ -467,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 @@ -493,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( From 5928eb3dde0e38662b0c9625827e708d7104a83d Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 14 Oct 2025 16:44:38 +0300 Subject: [PATCH 21/29] test(benefit): freeze time in some tests These tests have assumptions about time refs: HL-1625 --- .../benefit/applications/tests/test_applications_api.py | 8 ++++++++ backend/benefit/calculator/tests/test_calculator_api.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/backend/benefit/applications/tests/test_applications_api.py b/backend/benefit/applications/tests/test_applications_api.py index 4b2e0a1866..a49213459b 100755 --- a/backend/benefit/applications/tests/test_applications_api.py +++ b/backend/benefit/applications/tests/test_applications_api.py @@ -452,6 +452,7 @@ def test_application_single_read_as_handler( @pytest.mark.django_db +@pytest.mark.freeze_time("2021-06-04") def test_application_submitted_at( api_client, application, received_application, handling_application ): @@ -497,6 +498,7 @@ def test_application_post_success_unauthenticated(anonymous_client, application) @pytest.mark.django_db +@pytest.mark.freeze_time("2021-06-04") def test_application_post_success(api_client, application): """ Create a new application @@ -1123,6 +1125,7 @@ def test_application_date_range( ], ) @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 ): @@ -1290,6 +1293,7 @@ def test_apprenticeship_program_validation_on_submit( ], ) @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 ): @@ -1365,6 +1369,7 @@ 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, @@ -1606,6 +1611,7 @@ def add_attachments_to_application(request, application, is_handler_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 @@ -2281,6 +2287,7 @@ def test_application_additional_information_needed_by(api_client, handling_appli @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( @@ -2500,6 +2507,7 @@ def test_applications_with_unread_messages(api_client, handler_api_client, appli @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" diff --git a/backend/benefit/calculator/tests/test_calculator_api.py b/backend/benefit/calculator/tests/test_calculator_api.py index 19caaab9ed..07438d3039 100644 --- a/backend/benefit/calculator/tests/test_calculator_api.py +++ b/backend/benefit/calculator/tests/test_calculator_api.py @@ -706,6 +706,8 @@ def test_application_calculation_rows_id_exists( [(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 ): From 2a7ce00532405fe90e36334d44a708376371be56 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Tue, 14 Oct 2025 16:46:09 +0300 Subject: [PATCH 22/29] test(benefit): adjust date_start in ApplicationFactory This would previously generate applications that could not be submitted outside of January - April. Also add an extra assert to a test where this caused an issue. refs: HL-1625 --- backend/benefit/applications/tests/factories.py | 3 ++- backend/benefit/applications/tests/test_applications_api.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/benefit/applications/tests/factories.py b/backend/benefit/applications/tests/factories.py index b0099f63f4..8fc979a26f 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( diff --git a/backend/benefit/applications/tests/test_applications_api.py b/backend/benefit/applications/tests/test_applications_api.py index a49213459b..3a45c84e1b 100755 --- a/backend/benefit/applications/tests/test_applications_api.py +++ b/backend/benefit/applications/tests/test_applications_api.py @@ -1164,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 ) From fafda048bac4cad8f34392991e9efabcbe937bf4 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Wed, 15 Oct 2025 10:40:45 +0300 Subject: [PATCH 23/29] deps(benefit): migrate from pytest-freezegun to pytest-freezer pytest-freezegun is unmaintained and causes a DeprecationWarning refs: HL-1625 --- backend/benefit/requirements-dev.in | 2 +- backend/benefit/requirements-dev.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/benefit/requirements-dev.in b/backend/benefit/requirements-dev.in index f01de27d31..1a3547f587 100644 --- a/backend/benefit/requirements-dev.in +++ b/backend/benefit/requirements-dev.in @@ -8,6 +8,6 @@ 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 7bf90498f9..8cba02d3d7 100644 --- a/backend/benefit/requirements-dev.txt +++ b/backend/benefit/requirements-dev.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --cert=None --client-cert=None --index-url=None --pip-args=None --strip-extras requirements-dev.in +# pip-compile --strip-extras requirements-dev.in # asttokens==3.0.0 # via stack-data @@ -25,7 +25,7 @@ decorator==5.2.1 executing==2.2.1 # via stack-data freezegun==1.5.5 - # via pytest-freezegun + # via pytest-freezer idna==3.10 # via # -c requirements.txt @@ -81,12 +81,12 @@ pytest==8.4.2 # -r requirements-dev.in # pytest-cov # pytest-django - # pytest-freezegun + # pytest-freezer pytest-cov==7.0.0 # via -r requirements-dev.in 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.9.0.post0 # via From 5cb91953fdf385d5edd4999bce56b53f426f390a Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Wed, 15 Oct 2025 11:00:36 +0300 Subject: [PATCH 24/29] test(benefit): resolve post generation save deprecation warnings refs: HL-1625 --- backend/benefit/applications/tests/factories.py | 3 +++ backend/benefit/calculator/tests/factories.py | 1 + backend/benefit/terms/tests/factories.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/backend/benefit/applications/tests/factories.py b/backend/benefit/applications/tests/factories.py index 8fc979a26f..bffd4a37ab 100755 --- a/backend/benefit/applications/tests/factories.py +++ b/backend/benefit/applications/tests/factories.py @@ -161,6 +161,7 @@ def bases(self, created, extracted, **kwargs): class Meta: model = Application + skip_postgeneration_save = True attachment_factory_string = "applications.tests.factories.AttachmentFactory" @@ -230,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): @@ -371,6 +373,7 @@ class BaseApplicationBatchFactory(factory.django.DjangoModelFactory): class Meta: model = ApplicationBatch + skip_postgeneration_save = True class ApplicationBatchFactory(BaseApplicationBatchFactory): 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/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): From 806cf1ef37d9bdeedf0cae14327e7d0d027c8d82 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Wed, 15 Oct 2025 11:45:27 +0300 Subject: [PATCH 25/29] fix(benefit): use timezone.now instead of datetime.now Eliminates RuntimeWarnings from using naive datetimes refs: HL-1625 --- .../applications/api/v1/search_views.py | 20 +++++++++---------- .../benefit/applications/tests/conftest.py | 2 +- .../tests/test_application_search.py | 7 ++++--- backend/benefit/users/tests/test_gdpr_api.py | 3 ++- 4 files changed, 17 insertions(+), 15 deletions(-) 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/tests/conftest.py b/backend/benefit/applications/tests/conftest.py index e636d64ff3..7734bc83b9 100644 --- a/backend/benefit/applications/tests/conftest.py +++ b/backend/benefit/applications/tests/conftest.py @@ -883,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(), ) 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/users/tests/test_gdpr_api.py b/backend/benefit/users/tests/test_gdpr_api.py index 1380c9ca12..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 = { From 7c66764c35fde4fcb859841b5939dc7808754736 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Wed, 15 Oct 2025 13:31:44 +0300 Subject: [PATCH 26/29] build: apply sonar suggestions on docker-entrypoint.sh --- backend/benefit/docker-entrypoint.sh | 2 +- backend/kesaseteli/docker-entrypoint.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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..." From ebd745b7570b896f33c75fced37a848538ef7430 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Wed, 15 Oct 2025 16:59:40 +0300 Subject: [PATCH 27/29] test(benefit): fix test flakyness related to EmployeeFactory EmployeeFactory had a 1/5001 chance of generating a monthly_pay that would not pass validation. refs: HL-1625 --- backend/benefit/applications/tests/factories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/benefit/applications/tests/factories.py b/backend/benefit/applications/tests/factories.py index bffd4a37ab..c7482ba297 100755 --- a/backend/benefit/applications/tests/factories.py +++ b/backend/benefit/applications/tests/factories.py @@ -345,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) From 77e34d9d0362b82589ef760a5f025760e3af8b07 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Wed, 15 Oct 2025 17:03:02 +0300 Subject: [PATCH 28/29] test(benefit): enable and update previously disabled test This test was set to be skipped in 2023-08-30 due to flakiness. Presumably flakiness is now resolved so re-enabling. A new BOM feature was added in 2023-11-16 but because this test was skipped, it was presumably not updated. Added support for BOM. refs: HL-1625 --- .../tests/test_applications_report.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/backend/benefit/applications/tests/test_applications_report.py b/backend/benefit/applications/tests/test_applications_report.py index 0d02e662b2..64a9579626 100644 --- a/backend/benefit/applications/tests/test_applications_report.py +++ b/backend/benefit/applications/tests/test_applications_report.py @@ -50,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: @@ -59,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 @@ -84,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 @@ -94,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 @@ -127,12 +137,6 @@ def _create_applications_for_export(): @pytest.mark.django_db -@pytest.mark.skip( - reason=( - "This test fails in deploy pipeline - DETAIL: Key" - " (username)=(masonzachary_a45eb8) already exists." - ) -) def test_applications_csv_export_new_applications(handler_api_client): ( application1, @@ -148,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 @@ -167,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( @@ -189,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, @@ -197,6 +204,7 @@ 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 From 7f954d11eed05fce6dbec9cad4e665e521f8c8d0 Mon Sep 17 00:00:00 2001 From: Matti Eiden Date: Thu, 16 Oct 2025 15:19:22 +0300 Subject: [PATCH 29/29] test(benefit): use correct input data for testing record output These tests used incorrect input data, which could lead to flaky tests as Acquired timestamp could be off by one second. refs: HL-1625 --- backend/benefit/applications/tests/test_ahjo_payload.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/benefit/applications/tests/test_ahjo_payload.py b/backend/benefit/applications/tests/test_ahjo_payload.py index c7cc2ed349..bc99c4d536 100644 --- a/backend/benefit/applications/tests/test_ahjo_payload.py +++ b/backend/benefit/applications/tests/test_ahjo_payload.py @@ -346,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", @@ -430,7 +430,7 @@ def test_prepare_update_application_payload(decided_application): { "Title": f"{title}", "Type": AhjoRecordType.APPLICATION, - "Acquired": application.submitted_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",