From 7674209778a33ee006c0f1afc9ee0307ca8465bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Riku=20Kestila=CC=88?= Date: Wed, 22 Nov 2023 16:25:06 +0200 Subject: [PATCH 01/11] feat: add utility function for encoding multipart/form-data --- backend/benefit/common/tests/test_utils.py | 14 ++++++++++++++ backend/benefit/common/utils.py | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/backend/benefit/common/tests/test_utils.py b/backend/benefit/common/tests/test_utils.py index 785382f50f..5a079896ec 100644 --- a/backend/benefit/common/tests/test_utils.py +++ b/backend/benefit/common/tests/test_utils.py @@ -11,6 +11,7 @@ date_range_overlap, days360, duration_in_months, + encode_multipart_formdata, get_date_range_end_with_days360, hash_file, ) @@ -145,3 +146,16 @@ def test_hash_file(): # Assert that the actual hash matches the expected hash assert actual_hash == expected_hash + + +def test_encode_multipart_formdata(): + fields = {'field1': 'value1', 'field2': 'value2'} + body_str, content_type = encode_multipart_formdata(fields) + + assert body_str.startswith('--') + assert body_str.endswith('--\r\n') + assert 'field1' in body_str + assert 'value1' in body_str + assert 'field2' in body_str + assert 'value2' in body_str + assert content_type.startswith('multipart/form-data; boundary=') \ No newline at end of file diff --git a/backend/benefit/common/utils.py b/backend/benefit/common/utils.py index 910e2f5322..ff8c33b1f7 100644 --- a/backend/benefit/common/utils.py +++ b/backend/benefit/common/utils.py @@ -2,6 +2,7 @@ import functools import hashlib import itertools +import uuid from datetime import date, timedelta from typing import Iterator, Tuple, Union @@ -441,3 +442,20 @@ def hash_file(file: File) -> str: # Return the hexadecimal representation of the hash return sha256.hexdigest() + + +def encode_multipart_formdata(fields: dict) -> Tuple[str, str]: + boundary = uuid.uuid4().hex + body = [] + + for field, value in fields.items(): + body.append(f"--{boundary}") + body.append(f'Content-Disposition: form-data; name="{field}"') + body.append("") + body.append(value) + + body.append(f"--{boundary}--") + body.append("") + body_str = "\r\n".join(body) + content_type = f"multipart/form-data; boundary={boundary}" + return body_str, content_type From 49f3c3d6ffa243478379236695a50ab15a78f7fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Riku=20Kestila=CC=88?= Date: Wed, 22 Nov 2023 16:46:05 +0200 Subject: [PATCH 02/11] feat: open a case in Ahjo via Ahjo Rest API --- backend/benefit/applications/models.py | 8 + .../applications/services/ahjo_integration.py | 138 +++++++++++++++--- .../applications/services/ahjo_payload.py | 135 +++++++++++++++++ .../benefit/applications/tests/conftest.py | 79 ++++++++++ .../applications/tests/test_ahjo_payload.py | 95 ++++++++++++ .../applications/tests/test_ahjo_requests.py | 90 ++++++++++++ backend/benefit/helsinkibenefit/settings.py | 3 + 7 files changed, 526 insertions(+), 22 deletions(-) create mode 100644 backend/benefit/applications/services/ahjo_payload.py create mode 100644 backend/benefit/applications/tests/test_ahjo_payload.py create mode 100644 backend/benefit/applications/tests/test_ahjo_requests.py diff --git a/backend/benefit/applications/models.py b/backend/benefit/applications/models.py index 55f5c90abb..78f5983ba2 100755 --- a/backend/benefit/applications/models.py +++ b/backend/benefit/applications/models.py @@ -399,6 +399,14 @@ def total_deminimis_amount(self): total += deminimis_aid.amount return total + @property + def contact_person(self): + return ( + self.company_contact_person_first_name + + " " + + self.company_contact_person_last_name + ) + def get_log_entry_field(self, to_statuses, field_name): if ( log_entry := self.log_entries.filter(to_status__in=to_statuses) diff --git a/backend/benefit/applications/services/ahjo_integration.py b/backend/benefit/applications/services/ahjo_integration.py index 7bd2a6f46e..debc7719e4 100644 --- a/backend/benefit/applications/services/ahjo_integration.py +++ b/backend/benefit/applications/services/ahjo_integration.py @@ -1,5 +1,8 @@ +import json import logging import os +import urllib.request +import uuid import zipfile from collections import defaultdict from dataclasses import dataclass @@ -12,11 +15,14 @@ from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.db.models import QuerySet +from django.urls import reverse -from applications.enums import ApplicationStatus -from applications.models import AhjoSetting, Application +from applications.enums import AhjoStatus as AhjoStatusEnum, ApplicationStatus +from applications.models import AhjoSetting, AhjoStatus, Application from applications.services.ahjo_authentication import AhjoConnector +from applications.services.ahjo_payload import prepare_open_case_payload from applications.services.applications_csv_report import ApplicationsCsvService +from common.utils import encode_multipart_formdata from companies.models import Company @@ -358,44 +364,132 @@ def export_application_batch(batch) -> bytes: return generate_zip(pdf_files) -def dummy_ahjo_request(): - """Dummy function for preliminary testing of Ahjo integration""" - ahjo_api_url = settings.AHJO_REST_API_URL +def get_token() -> str: + """Get the access token from Ahjo Service.""" try: ahjo_auth_code = AhjoSetting.objects.get(name="ahjo_code").data LOGGER.info(f"Retrieved auth code: {ahjo_auth_code}") + connector = AhjoConnector(requests) + + if not connector.is_configured(): + LOGGER.warning("AHJO connector is not configured") + return + return connector.get_access_token(ahjo_auth_code["code"]) except ObjectDoesNotExist: LOGGER.error( "Error: Ahjo auth code not found in database. Please set the 'ahjo_code' setting." ) return - - connector = AhjoConnector(requests) - - if not connector.is_configured(): - LOGGER.warning("AHJO connector is not configured") - return - try: - ahjo_token = connector.get_access_token(ahjo_auth_code["code"]) except Exception as e: LOGGER.warning(f"Error retrieving access token: {e}") return - headers = { - "Authorization": f"Bearer {ahjo_token.access_token}", + + +def prepare_headers(access_token: str, application_uuid: uuid) -> dict: + """Prepare the headers for the Ahjo request.""" + url = reverse("ahjo_callback_url", kwargs={"uuid": str(application_uuid)}) + + return { + "Authorization": f"Bearer {access_token}", + "Accept": "application/hal+json", + "X-CallbackURL": f"{settings.API_BASE_URL}{url}", } - print(headers) + + +def get_application(id: uuid) -> Optional[Application]: + """Get the first accepted application.""" + application = ( + Application.objects.filter(pk=id, status=ApplicationStatus.ACCEPTED) + .prefetch_related("attachments", "calculation", "company") + .first() + ) + if not application: + LOGGER.info("No applications found for Ahjo request.") + # Check that the handler has an ad_username set, if not, log an error and return None + if not application.calculation.handler.ad_username: + LOGGER.error("No ad_username set for the handler for Ahjo request.") + return None + return application + + +def create_status_for_application(application: Application): + """Create a new AhjoStatus for the application.""" + AhjoStatus.objects.create( + application=application, status=AhjoStatusEnum.REQUEST_TO_OPEN_CASE_SENT + ) + + +def do_ahjo_request_with_form_data( + url: str, + headers: dict, + data: dict, + application: Application, +): + json_data = json.dumps(data) + form_data, content_type = encode_multipart_formdata({"case": json_data}) + + headers["Content-Type"] = content_type + + try: + request = urllib.request.Request( + f"{url}/cases", + method="POST", + headers=headers, + data=form_data.encode("utf-8"), + ) + + with urllib.request.urlopen(request) as response: + response_data = response.read() + print(response.status) + print(response_data.decode("utf-8")) + + create_status_for_application(application) + except Exception as e: + # Handle any other error + LOGGER.error(f"Error occurred: {e}") + + +def do_ahjo_request_with_json_payload( + url: str, headers: dict, data: dict, application: Application, timeout: int = 10 +): + headers["Content-Type"] = "application/json" + + json_data = json.dumps(data) + try: - response = requests.get( - f"{ahjo_api_url}/cases", headers=headers, timeout=connector.timout + response = requests.post( + f"{url}/cases", headers=headers, timeout=timeout, data=json_data ) response.raise_for_status() - print(response.json()) + + if response.ok: + create_status_for_application(application) + LOGGER.info( + f"Open case for application {application.id} Request to Ahjo was successful." + ) + except requests.exceptions.HTTPError as e: # Handle the HTTP error - LOGGER.error(f"HTTP error occurred: {e}") + LOGGER.error(f"HTTP error occurred while sending request to Ahjo: {e}") except requests.exceptions.RequestException as e: # Handle the network error - LOGGER.errror(f"Network error occurred: {e}") + LOGGER.error(f"Network error occurred while sending request to Ahjo: {e}") except Exception as e: # Handle any other error - LOGGER.error(f"Error occurred: {e}") + LOGGER.error(f"Error occurred while sending request to Ahjo: {e}") + + +def open_case_in_ahjo(application_id: uuid): + """Open a case in Ahjo.""" + application = get_application(application_id) + # if no suitable application is found, or the handler has no ad_id, bail out + if not application: + return + + ahjo_api_url = settings.AHJO_REST_API_URL + ahjo_token = get_token() + headers = prepare_headers(ahjo_token.access_token, application.id) + data = prepare_open_case_payload(application) + + # do_ahjo_request_with_form_data(ahjo_api_url, headers, data, application) + do_ahjo_request_with_json_payload(ahjo_api_url, headers, data, application) diff --git a/backend/benefit/applications/services/ahjo_payload.py b/backend/benefit/applications/services/ahjo_payload.py new file mode 100644 index 0000000000..c01cafa92c --- /dev/null +++ b/backend/benefit/applications/services/ahjo_payload.py @@ -0,0 +1,135 @@ +import uuid +from datetime import datetime +from typing import List, Union + +from django.conf import settings +from django.urls import reverse +from django.utils.translation import gettext as _ + +from applications.models import Application, Attachment +from common.utils import hash_file +from users.models import User + + +def _prepare_top_level_dict(application: Application, case_records: List[dict]) -> dict: + """Prepare the dictionary that is sent to Ahjo""" + application_date = application.created_at.isoformat() + application_year = application.created_at.year + title = f"Avustuksen myöntäminen, työllisyyspalvelut, \ +työnantajan Helsinki-lisä vuonna {application.created_at.year}, \ +työnantaja {application.company_name}" + + case_dict = { + "Title": title, + "Acquired": application_date, + "ClassificationCode": "02 05 01 00", + "ClassificationTitle": "Kunnan myöntämät avustukset", + "Language": "fi", + "PublicityClass": "Julkinen", + "InternalTitle": f"Avustuksen myöntäminen, työllisyyspalvelut, \ + työnantajan Helsinki-lisä vuonna {application_year}, \ + työnantaja {application.company_name}", + "Subjects": [ + {"Subject": "Helsinki-lisät", "Scheme": "hki-yhpa"}, + {"Subject": "kunnan myöntämät avustukset", "Scheme": "hki-yhpa"}, + {"Subject": "työnantajat", "Scheme": "hki-yhpa"}, + {"Subject": "työllisyydenhoito"}, + ], + "PersonalData": "Sisältää erityisiä henkilötietoja", + "Reference": application.application_number, + "Records": case_records, + "Agents": [ + { + "Role": "sender_initiator", + "CorporateName": application.company.name, + "ContactPerson": application.contact_person, + "Type": "ExterOnal", + "Email": application.company_contact_person_email, + "AddressStreet": application.company.street_address, + "AddressPostalCode": application.company.postcode, + "AddressCity": application.company.city, + } + ], + } + return case_dict + + +def _prepare_record_document_dict(attachment: Attachment) -> dict: + """Prepare a documents dict for a record""" + # If were running in mock mode, use the local file URI + file_url = reverse("ahjo_attachment_url", kwargs={"uuid": attachment.id}) + hash_value = hash_file(attachment.attachment_file) + return { + "FileName": f"{attachment.attachment_file.name}", + "FormatName": f"{attachment.content_type}", + "HashAlgorithm": "sha256", + "HashValue": hash_value, + "FileURI": f"{settings.API_BASE_URL}{file_url}", + } + + +def _prepare_record( + record_title: str, + record_type: str, + acquired: datetime, + reference: Union[int, uuid.UUID], + documents: List[dict], + handler: User, + publicity_class: str = "Salassa pidettävä", +): + """Prepare a single record dict for Ahjo.""" + + return { + "Title": record_title, + "Type": record_type, + "Acquired": acquired, + "PublicityClass": publicity_class, + "SecurityReasons": ["JulkL (621/1999) 24.1 § 25 k"], + "Language": "fi", + "PersonalData": "Sisältää erityisiä henkilötietoja", + "Reference": str(reference), + "Documents": documents, + "Agents": [ + { + "Role": "mainCreator", + "Name": f"{handler.last_name}, {handler.first_name}", + "ID": handler.ad_username, + } + ], + } + + +def _prepare_case_records(application: Application) -> List[dict]: + """Prepare the list of case records""" + case_records = [] + handler = application.calculation.handler + main_document_record = _prepare_record( + "Hakemus", + "hakemus", + application.created_at.isoformat(), + application.application_number, + [], # TODO Pdf version of the application goes here with prepare_record() + handler, + ) + + case_records.append(main_document_record) + + for attachment in application.attachments.all(): + document_record = _prepare_record( + "Hakemuksen Liite", + "liite", + attachment.created_at.isoformat(), + attachment.id, + [_prepare_record_document_dict(attachment)], + handler, + ) + case_records.append(document_record) + + return case_records + + +def prepare_open_case_payload(application: Application) -> dict: + "Prepare the complete dictionary payload that is sent to Ahjo" + case_records = _prepare_case_records(application) + payload = _prepare_top_level_dict(application, case_records) + return payload diff --git a/backend/benefit/applications/tests/conftest.py b/backend/benefit/applications/tests/conftest.py index 8e8ba1c98f..8c6aa1a981 100755 --- a/backend/benefit/applications/tests/conftest.py +++ b/backend/benefit/applications/tests/conftest.py @@ -9,6 +9,7 @@ from applications.enums import ApplicationStatus, BenefitType from applications.models import Application +from applications.services.ahjo_payload import prepare_open_case_payload from applications.services.applications_csv_report import ApplicationsCsvService from applications.tests.factories import ( ApplicationBatchFactory, @@ -257,6 +258,79 @@ def set_debug_to_false(settings): settings.DEBUG = False +@pytest.fixture() +def ahjo_payload_record(decided_application): + application = decided_application + + record_title = "Hakemus" + record_type = "hakemus" + acquired = application.created_at.isoformat() + reference = str(application.application_number) + documents = [] + agent = application.calculation.handler + publicity_class = "Salassa pidettävä" + + return { + "Title": record_title, + "Type": record_type, + "Acquired": acquired, + "PublicityClass": publicity_class, + "SecurityReasons": ["JulkL (621/1999) 24.1 § 25 k"], + "Language": "fi", + "PersonalData": "Sisältää erityisiä henkilötietoja", + "Reference": reference, + "Documents": documents, + "Agents": [ + { + "Role": "mainCreator", + "Name": f"{agent.last_name}, {agent.first_name}", + "ID": agent.ad_username, + } + ], + } + + +@pytest.fixture() +def ahjo_open_case_top_level_dict(decided_application): + application = decided_application + title = f"Avustuksen myöntäminen, työllisyyspalvelut, \ +työnantajan Helsinki-lisä vuonna {application.created_at.year}, \ +työnantaja {application.company_name}" + + return { + "Title": title, + "Acquired": application.created_at.isoformat(), + "ClassificationCode": "02 05 01 00", + "ClassificationTitle": "Kunnan myöntämät avustukset", + "Language": "fi", + "PublicityClass": "Julkinen", + "InternalTitle": f"Avustuksen myöntäminen, työllisyyspalvelut, \ + työnantajan Helsinki-lisä vuonna {application.created_at.year}, \ + työnantaja {application.company_name}", + "Subjects": [ + {"Subject": "Helsinki-lisät", "Scheme": "hki-yhpa"}, + {"Subject": "kunnan myöntämät avustukset", "Scheme": "hki-yhpa"}, + {"Subject": "työnantajat", "Scheme": "hki-yhpa"}, + {"Subject": "työllisyydenhoito"}, + ], + "PersonalData": "Sisältää erityisiä henkilötietoja", + "Reference": application.application_number, + "Records": [], + "Agents": [ + { + "Role": "sender_initiator", + "CorporateName": application.company.name, + "ContactPerson": application.contact_person, + "Type": "ExterOnal", + "Email": application.company_contact_person_email, + "AddressStreet": application.company.street_address, + "AddressPostalCode": application.company.postcode, + "AddressCity": application.company.city, + } + ], + } + + def split_lines_at_semicolon(csv_string): # split CSV into lines and columns without using the csv library csv_lines = csv_string.splitlines() @@ -279,3 +353,8 @@ def pytest_sessionfinish(session, exitstatus): except OSError as e: print(f"Error while deleting file in media folder: {e}") print(f"\nTests finished, deleted {number_of_files} files in the media folder") + + +@pytest.fixture() +def ahjo_open_case_payload(decided_application): + return prepare_open_case_payload(decided_application) diff --git a/backend/benefit/applications/tests/test_ahjo_payload.py b/backend/benefit/applications/tests/test_ahjo_payload.py new file mode 100644 index 0000000000..b3fe4e9efa --- /dev/null +++ b/backend/benefit/applications/tests/test_ahjo_payload.py @@ -0,0 +1,95 @@ +from django.urls import reverse + +from applications.services.ahjo_payload import ( + _prepare_case_records, + _prepare_record, + _prepare_record_document_dict, + _prepare_top_level_dict, +) +from common.utils import hash_file + + +def test_prepare_record(decided_application, ahjo_payload_record): + application = decided_application + + record = _prepare_record( + ahjo_payload_record["Title"], + ahjo_payload_record["Type"], + application.created_at.isoformat(), + application.application_number, + [], + application.calculation.handler, + ) + + assert ahjo_payload_record == record + + +def test_prepare_record_document_dict(decided_application, settings): + settings.DEBUG = True + settings.API_BASE_URL = "http://test.com" + attachment = decided_application.attachments.first() + hash_value = hash_file(attachment.attachment_file) + file_url = reverse("ahjo_attachment_url", kwargs={"uuid": attachment.id}) + + want = { + "FileName": attachment.attachment_file.name, + "FormatName": attachment.content_type, + "HashAlgorithm": "sha256", + "HashValue": hash_value, + "FileURI": f"{settings.API_BASE_URL}{file_url}", + } + + got = _prepare_record_document_dict(attachment) + + assert want == got + + +def test_prepare_case_records(decided_application, settings): + settings.DEBUG = True + application = decided_application + handler = application.calculation.handler + handler_name = f"{handler.last_name}, {handler.first_name}" + want = [ + { + "Title": "Hakemus", + "Type": "hakemus", + "Acquired": application.created_at.isoformat(), + "PublicityClass": "Salassa pidettävä", + "SecurityReasons": ["JulkL (621/1999) 24.1 § 25 k"], + "Language": "fi", + "PersonalData": "Sisältää erityisiä henkilötietoja", + "Reference": str(application.application_number), + "Documents": [], + "Agents": [ + { + "Role": "mainCreator", + "Name": handler_name, + "ID": handler.ad_username, + } + ], + } + ] + + for attachment in application.attachments.all(): + document_record = _prepare_record( + "Hakemuksen Liite", + "liite", + attachment.created_at.isoformat(), + attachment.id, + [_prepare_record_document_dict(attachment)], + handler, + ) + + want.append(document_record) + + got = _prepare_case_records(application) + + assert want == got + + +def test_prepare_top_level_dict(decided_application, ahjo_open_case_top_level_dict): + application = decided_application + + got = _prepare_top_level_dict(application, []) + + assert ahjo_open_case_top_level_dict == got diff --git a/backend/benefit/applications/tests/test_ahjo_requests.py b/backend/benefit/applications/tests/test_ahjo_requests.py new file mode 100644 index 0000000000..f490d6b2be --- /dev/null +++ b/backend/benefit/applications/tests/test_ahjo_requests.py @@ -0,0 +1,90 @@ +import uuid +from unittest.mock import patch + +import pytest +import requests +import requests_mock +from django.urls import reverse + +from applications.enums import AhjoStatus +from applications.services.ahjo_integration import ( + do_ahjo_request_with_json_payload, + prepare_headers, +) + + +def test_prepare_headers(settings): + settings.API_BASE_URL = "http://test.com" + access_token = "test_token" + application_uuid = uuid.uuid4() + + headers = prepare_headers(access_token, application_uuid) + + url = reverse("ahjo_callback_url", kwargs={"uuid": str(application_uuid)}) + expected_headers = { + "Authorization": f"Bearer {access_token}", + "Accept": "application/hal+json", + "X-CallbackURL": f"{settings.API_BASE_URL}{url}", + } + + assert headers == expected_headers + + +def test_do_ahjo_request_with_json_payload_success( + decided_application, ahjo_open_case_payload +): + url = "http://test.com" + headers = {"Authorization": "Bearer test"} + + with requests_mock.Mocker() as m: + m.post(f"{url}/cases", text="data") + do_ahjo_request_with_json_payload( + url, headers, ahjo_open_case_payload, decided_application + ) + decided_application.refresh_from_db() + assert ( + decided_application.ahjo_status.latest().status + == AhjoStatus.REQUEST_TO_OPEN_CASE_SENT + ) + + +@patch("applications.services.ahjo_integration.LOGGER") +def test_http_error(mock_logger, decided_application, ahjo_open_case_payload): + url = "http://mockedurl.com" + headers = {} + data = ahjo_open_case_payload + application = decided_application + + with requests_mock.Mocker() as m: + m.post(f"{url}/cases", status_code=400) + do_ahjo_request_with_json_payload(url, headers, data, application) + + mock_logger.error.assert_called() + + +@patch("applications.services.ahjo_integration.LOGGER") +def test_network_error(mock_logger, decided_application, ahjo_open_case_payload): + url = "http://mockedurl.com" + headers = {} + data = ahjo_open_case_payload + application = decided_application + + with requests_mock.Mocker() as m: + m.post(f"{url}/cases", exc=requests.exceptions.ConnectionError) + do_ahjo_request_with_json_payload(url, headers, data, application) + + mock_logger.error.assert_called() + + +@patch("applications.services.ahjo_integration.LOGGER") +def test_other_exception(mock_logger, decided_application, ahjo_open_case_payload): + url = "http://mockedurl.com" + headers = {} + data = ahjo_open_case_payload + application = decided_application + + with requests_mock.Mocker() as m: + m.post(f"{url}/cases", exc=Exception("Some error")) + do_ahjo_request_with_json_payload(url, headers, data, application) + + mock_logger.error.assert_called() diff --git a/backend/benefit/helsinkibenefit/settings.py b/backend/benefit/helsinkibenefit/settings.py index 2acddb29cc..ac5ba0c56c 100644 --- a/backend/benefit/helsinkibenefit/settings.py +++ b/backend/benefit/helsinkibenefit/settings.py @@ -20,6 +20,7 @@ default_var_root = environ.Path(checkout_dir("var")) env = environ.Env( + API_BASE_URL=(str, "https://localhost:8000"), DEBUG=(bool, False), SECRET_KEY=(str, ""), MEDIA_ROOT=(environ.Path(), default_var_root("media")), @@ -170,6 +171,8 @@ os.environ["HTTPS"] = "on" +API_BASE_URL = env.str("API_BASE_URL") + BASE_DIR = str(checkout_dir) DEBUG = env.bool("DEBUG") From 02a9c9519590561efe642c0b6ea98040fac765c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Riku=20Kestila=CC=88?= Date: Wed, 29 Nov 2023 09:54:09 +0200 Subject: [PATCH 03/11] fix: typo --- backend/benefit/applications/services/ahjo_authentication.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/benefit/applications/services/ahjo_authentication.py b/backend/benefit/applications/services/ahjo_authentication.py index 8bcde2dcf9..4f44e7ddb7 100644 --- a/backend/benefit/applications/services/ahjo_authentication.py +++ b/backend/benefit/applications/services/ahjo_authentication.py @@ -29,7 +29,7 @@ def __init__(self, requests_module: requests.Session = requests) -> None: self.headers: Dict[str, str] = { "Content-Type": "application/x-www-form-urlencoded", } - self.timout: int = 10 + self.timeout: int = 10 def is_configured(self) -> bool: """Check if all required config options are set""" @@ -86,7 +86,7 @@ def refresh_token(self) -> AhjoToken: def do_token_request(self, payload: Dict[str, str]) -> AhjoToken: # Make the POST request response = self.requests_module.post( - self.token_url, headers=self.headers, data=payload, timeout=self.timout + self.token_url, headers=self.headers, data=payload, timeout=self.timeout ) # Check if the request was successful From 87e9ac61ae599d2a3c2db61df4c571a5c62091c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Riku=20Kestila=CC=88?= Date: Wed, 29 Nov 2023 09:55:30 +0200 Subject: [PATCH 04/11] fix: code style/imports --- .../applications/services/ahjo_payload.py | 1 - .../applications/tests/test_ahjo_requests.py | 1 - backend/benefit/common/tests/test_utils.py | 16 ++++++++-------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/backend/benefit/applications/services/ahjo_payload.py b/backend/benefit/applications/services/ahjo_payload.py index c01cafa92c..2caf2c51a3 100644 --- a/backend/benefit/applications/services/ahjo_payload.py +++ b/backend/benefit/applications/services/ahjo_payload.py @@ -4,7 +4,6 @@ from django.conf import settings from django.urls import reverse -from django.utils.translation import gettext as _ from applications.models import Application, Attachment from common.utils import hash_file diff --git a/backend/benefit/applications/tests/test_ahjo_requests.py b/backend/benefit/applications/tests/test_ahjo_requests.py index f490d6b2be..4fea054f87 100644 --- a/backend/benefit/applications/tests/test_ahjo_requests.py +++ b/backend/benefit/applications/tests/test_ahjo_requests.py @@ -1,7 +1,6 @@ import uuid from unittest.mock import patch -import pytest import requests import requests_mock from django.urls import reverse diff --git a/backend/benefit/common/tests/test_utils.py b/backend/benefit/common/tests/test_utils.py index 5a079896ec..9edcf0f68e 100644 --- a/backend/benefit/common/tests/test_utils.py +++ b/backend/benefit/common/tests/test_utils.py @@ -149,13 +149,13 @@ def test_hash_file(): def test_encode_multipart_formdata(): - fields = {'field1': 'value1', 'field2': 'value2'} + fields = {"field1": "value1", "field2": "value2"} body_str, content_type = encode_multipart_formdata(fields) - assert body_str.startswith('--') - assert body_str.endswith('--\r\n') - assert 'field1' in body_str - assert 'value1' in body_str - assert 'field2' in body_str - assert 'value2' in body_str - assert content_type.startswith('multipart/form-data; boundary=') \ No newline at end of file + assert body_str.startswith("--") + assert body_str.endswith("--\r\n") + assert "field1" in body_str + assert "value1" in body_str + assert "field2" in body_str + assert "value2" in body_str + assert content_type.startswith("multipart/form-data; boundary=") From 0db7fca585a4d736618798e51869cb64303eaa3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Riku=20Kestila=CC=88?= Date: Fri, 1 Dec 2023 14:33:23 +0200 Subject: [PATCH 05/11] fix: change to f-string for consistency --- backend/benefit/applications/models.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/backend/benefit/applications/models.py b/backend/benefit/applications/models.py index 78f5983ba2..25faf5b1da 100755 --- a/backend/benefit/applications/models.py +++ b/backend/benefit/applications/models.py @@ -401,11 +401,7 @@ def total_deminimis_amount(self): @property def contact_person(self): - return ( - self.company_contact_person_first_name - + " " - + self.company_contact_person_last_name - ) + return f"{self.company_contact_person_first_name} {self.company_contact_person_last_name}" def get_log_entry_field(self, to_statuses, field_name): if ( From a631a6fd7acb7c40213c132f45438dbf5a3a42c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Riku=20Kestila=CC=88?= Date: Fri, 1 Dec 2023 14:36:38 +0200 Subject: [PATCH 06/11] fix: remove non-pythonic injection of requests module --- .../services/ahjo_authentication.py | 11 +- .../applications/services/ahjo_integration.py | 2 +- .../tests/test_ahjo_authentication.py | 105 +++++++++--------- 3 files changed, 59 insertions(+), 59 deletions(-) diff --git a/backend/benefit/applications/services/ahjo_authentication.py b/backend/benefit/applications/services/ahjo_authentication.py index 4f44e7ddb7..5a2f2347f7 100644 --- a/backend/benefit/applications/services/ahjo_authentication.py +++ b/backend/benefit/applications/services/ahjo_authentication.py @@ -4,7 +4,7 @@ import requests from django.conf import settings -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from applications.models import AhjoSetting @@ -17,8 +17,7 @@ class AhjoToken: class AhjoConnector: - def __init__(self, requests_module: requests.Session = requests) -> None: - self.requests_module: requests = requests_module + def __init__(self) -> None: self.token_url: str = settings.AHJO_TOKEN_URL self.client_id: str = settings.AHJO_CLIENT_ID self.client_secret: str = settings.AHJO_CLIENT_SECRET @@ -56,7 +55,7 @@ def get_new_token(self, auth_code: str) -> AhjoToken: this is only used when getting the initial token or when the token has expired. """ if not auth_code: - raise Exception("No auth code") + raise ImproperlyConfigured("no auth code configured") payload = { "client_id": self.client_id, "client_secret": self.client_secret, @@ -72,7 +71,7 @@ def refresh_token(self) -> AhjoToken: """ token = self.get_token_from_db() if not token.refresh_token: - raise Exception("No refresh token") + raise ImproperlyConfigured("No refresh token configured") payload = { "client_id": self.client_id, @@ -85,7 +84,7 @@ def refresh_token(self) -> AhjoToken: def do_token_request(self, payload: Dict[str, str]) -> AhjoToken: # Make the POST request - response = self.requests_module.post( + response = requests.post( self.token_url, headers=self.headers, data=payload, timeout=self.timeout ) diff --git a/backend/benefit/applications/services/ahjo_integration.py b/backend/benefit/applications/services/ahjo_integration.py index debc7719e4..e08df5382f 100644 --- a/backend/benefit/applications/services/ahjo_integration.py +++ b/backend/benefit/applications/services/ahjo_integration.py @@ -369,7 +369,7 @@ def get_token() -> str: try: ahjo_auth_code = AhjoSetting.objects.get(name="ahjo_code").data LOGGER.info(f"Retrieved auth code: {ahjo_auth_code}") - connector = AhjoConnector(requests) + connector = AhjoConnector() if not connector.is_configured(): LOGGER.warning("AHJO connector is not configured") diff --git a/backend/benefit/applications/tests/test_ahjo_authentication.py b/backend/benefit/applications/tests/test_ahjo_authentication.py index 08a5d89951..7ef8841b0b 100644 --- a/backend/benefit/applications/tests/test_ahjo_authentication.py +++ b/backend/benefit/applications/tests/test_ahjo_authentication.py @@ -1,29 +1,32 @@ from datetime import datetime, timedelta -from unittest.mock import Mock import pytest -import requests +import requests_mock +from django.core.exceptions import ImproperlyConfigured from applications.models import AhjoSetting from applications.services.ahjo_authentication import AhjoConnector, AhjoToken @pytest.fixture -def requests_mock(): - return Mock(spec=requests.Session) +def ahjo_connector(): + return AhjoConnector() @pytest.fixture -def ahjo_connector(requests_mock: Mock): - return AhjoConnector(requests_mock) +def token_response(): + return { + "access_token": "access_token", + "refresh_token": "refresh_token", + "expires_in": "3600", + } -def test_is_configured(ahjo_connector: AhjoConnector): - # Test with all config options set - ahjo_connector.token_url = "https://example.com/token" - ahjo_connector.client_id = "client_id" - ahjo_connector.client_secret = "client_secret" - ahjo_connector.redirect_uri = "https://example.com/callback" +def test_is_configured(ahjo_connector, settings): + settings.AHJO_TOKEN_URL = "http://example.com/token" + settings.AHJO_CLIENT_ID = "client_id" + settings.AHJO_CLIENT_SECRET = "client_secret" + settings.AHJO_REDIRECT_URL = "http://example.com/redirect" assert ahjo_connector.is_configured() is True # Test with missing config options @@ -31,45 +34,44 @@ def test_is_configured(ahjo_connector: AhjoConnector): assert ahjo_connector.is_configured() is False -def test_get_new_token(requests_mock, ahjo_connector: AhjoConnector): +def test_get_new_token(ahjo_connector, token_response): # Test with valid auth code - requests_mock.post.return_value.status_code = 200 - requests_mock.post.return_value.json.return_value = { - "access_token": "access_token", - "refresh_token": "refresh_token", - "expires_in": "3600", - } - token = ahjo_connector.get_new_token("auth_code") - assert token.access_token == "access_token" - assert token.refresh_token == "refresh_token" - assert isinstance(token.expires_in, datetime) + with requests_mock.Mocker() as m: + m.post( + "https://johdontyopoytahyte.hel.fi/ids4/connect/token", + json=token_response, + ) + + token = ahjo_connector.get_new_token("authcode123") + assert isinstance(token.expires_in, datetime) + assert token.access_token == "access_token" + assert token.refresh_token == "refresh_token" # Test with missing auth code - with pytest.raises(Exception): + with pytest.raises(ImproperlyConfigured): ahjo_connector.get_new_token("") -def test_refresh_token(requests_mock, ahjo_connector: AhjoConnector): +def test_refresh_token(ahjo_connector, token_response): # Test with valid refresh token AhjoSetting.objects.create( name="ahjo_access_token", data={ - "access_token": "dummy token", + "access_token": "access_token", "refresh_token": "refresh_token", "expires_in": datetime.now().isoformat(), }, ) + with requests_mock.Mocker() as m: + m.post( + "https://johdontyopoytahyte.hel.fi/ids4/connect/token", + json=token_response, + ) - requests_mock.post.return_value.status_code = 200 - requests_mock.post.return_value.json.return_value = { - "access_token": "new_access_token", - "refresh_token": "new_refresh_token", - "expires_in": "3600", - } - token = ahjo_connector.refresh_token() - assert token.access_token == "new_access_token" - assert token.refresh_token == "new_refresh_token" - assert isinstance(token.expires_in, datetime) + token = ahjo_connector.refresh_token() + assert token.access_token == "access_token" + assert token.refresh_token == "refresh_token" + assert isinstance(token.expires_in, datetime) # Test with missing refresh token AhjoSetting.objects.all().delete() @@ -77,24 +79,23 @@ def test_refresh_token(requests_mock, ahjo_connector: AhjoConnector): ahjo_connector.refresh_token() -def test_do_token_request(requests_mock, ahjo_connector: AhjoConnector): +def test_do_token_request(ahjo_connector: AhjoConnector, token_response): # Test with successful request - requests_mock.post.return_value.status_code = 200 - requests_mock.post.return_value.json.return_value = { - "access_token": "access_token", - "refresh_token": "refresh_token", - "expires_in": "3600", - } - payload = {"grant_type": "authorization_code", "code": "auth_code"} - token = ahjo_connector.do_token_request(payload) - assert token.access_token == "access_token" - assert token.refresh_token == "refresh_token" - assert isinstance(token.expires_in, datetime) - # Test with failed request - requests_mock.post.return_value.status_code = 400 - with pytest.raises(Exception): - ahjo_connector.do_token_request(payload) + with requests_mock.Mocker() as m: + m.post( + "https://johdontyopoytahyte.hel.fi/ids4/connect/token", json=token_response + ) + + token = ahjo_connector.do_token_request({}) + assert token.access_token == "access_token" + assert token.refresh_token == "refresh_token" + assert isinstance(token.expires_in, datetime) + + # Test with failed request + m.post("https://johdontyopoytahyte.hel.fi/ids4/connect/token", status_code=400) + with pytest.raises(Exception): + ahjo_connector.do_token_request({}) def test_get_token_from_db(ahjo_connector: AhjoConnector): From 38220e64c4a527b0ef4c7a2e6e372f3bc6b34192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Riku=20Kestila=CC=88?= Date: Fri, 1 Dec 2023 14:41:02 +0200 Subject: [PATCH 07/11] fix: remove unneeded form data request implementation --- .../applications/services/ahjo_integration.py | 33 ------------------- backend/benefit/common/tests/test_utils.py | 14 -------- backend/benefit/common/utils.py | 18 ---------- 3 files changed, 65 deletions(-) diff --git a/backend/benefit/applications/services/ahjo_integration.py b/backend/benefit/applications/services/ahjo_integration.py index e08df5382f..dd92072c1e 100644 --- a/backend/benefit/applications/services/ahjo_integration.py +++ b/backend/benefit/applications/services/ahjo_integration.py @@ -1,7 +1,6 @@ import json import logging import os -import urllib.request import uuid import zipfile from collections import defaultdict @@ -22,7 +21,6 @@ from applications.services.ahjo_authentication import AhjoConnector from applications.services.ahjo_payload import prepare_open_case_payload from applications.services.applications_csv_report import ApplicationsCsvService -from common.utils import encode_multipart_formdata from companies.models import Company @@ -419,36 +417,6 @@ def create_status_for_application(application: Application): ) -def do_ahjo_request_with_form_data( - url: str, - headers: dict, - data: dict, - application: Application, -): - json_data = json.dumps(data) - form_data, content_type = encode_multipart_formdata({"case": json_data}) - - headers["Content-Type"] = content_type - - try: - request = urllib.request.Request( - f"{url}/cases", - method="POST", - headers=headers, - data=form_data.encode("utf-8"), - ) - - with urllib.request.urlopen(request) as response: - response_data = response.read() - print(response.status) - print(response_data.decode("utf-8")) - - create_status_for_application(application) - except Exception as e: - # Handle any other error - LOGGER.error(f"Error occurred: {e}") - - def do_ahjo_request_with_json_payload( url: str, headers: dict, data: dict, application: Application, timeout: int = 10 ): @@ -491,5 +459,4 @@ def open_case_in_ahjo(application_id: uuid): headers = prepare_headers(ahjo_token.access_token, application.id) data = prepare_open_case_payload(application) - # do_ahjo_request_with_form_data(ahjo_api_url, headers, data, application) do_ahjo_request_with_json_payload(ahjo_api_url, headers, data, application) diff --git a/backend/benefit/common/tests/test_utils.py b/backend/benefit/common/tests/test_utils.py index 9edcf0f68e..785382f50f 100644 --- a/backend/benefit/common/tests/test_utils.py +++ b/backend/benefit/common/tests/test_utils.py @@ -11,7 +11,6 @@ date_range_overlap, days360, duration_in_months, - encode_multipart_formdata, get_date_range_end_with_days360, hash_file, ) @@ -146,16 +145,3 @@ def test_hash_file(): # Assert that the actual hash matches the expected hash assert actual_hash == expected_hash - - -def test_encode_multipart_formdata(): - fields = {"field1": "value1", "field2": "value2"} - body_str, content_type = encode_multipart_formdata(fields) - - assert body_str.startswith("--") - assert body_str.endswith("--\r\n") - assert "field1" in body_str - assert "value1" in body_str - assert "field2" in body_str - assert "value2" in body_str - assert content_type.startswith("multipart/form-data; boundary=") diff --git a/backend/benefit/common/utils.py b/backend/benefit/common/utils.py index ff8c33b1f7..910e2f5322 100644 --- a/backend/benefit/common/utils.py +++ b/backend/benefit/common/utils.py @@ -2,7 +2,6 @@ import functools import hashlib import itertools -import uuid from datetime import date, timedelta from typing import Iterator, Tuple, Union @@ -442,20 +441,3 @@ def hash_file(file: File) -> str: # Return the hexadecimal representation of the hash return sha256.hexdigest() - - -def encode_multipart_formdata(fields: dict) -> Tuple[str, str]: - boundary = uuid.uuid4().hex - body = [] - - for field, value in fields.items(): - body.append(f"--{boundary}") - body.append(f'Content-Disposition: form-data; name="{field}"') - body.append("") - body.append(value) - - body.append(f"--{boundary}--") - body.append("") - body_str = "\r\n".join(body) - content_type = f"multipart/form-data; boundary={boundary}" - return body_str, content_type From 180622ccb6f3ea1373aa4fda08d42f597999603b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Riku=20Kestila=CC=88?= Date: Fri, 1 Dec 2023 14:52:59 +0200 Subject: [PATCH 08/11] fix: clarify error handling --- .../applications/services/ahjo_integration.py | 35 ++++++++++--------- .../tests/test_ahjo_integration.py | 24 +++++++++++++ 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/backend/benefit/applications/services/ahjo_integration.py b/backend/benefit/applications/services/ahjo_integration.py index dd92072c1e..ad8debc1b7 100644 --- a/backend/benefit/applications/services/ahjo_integration.py +++ b/backend/benefit/applications/services/ahjo_integration.py @@ -12,7 +12,7 @@ import pdfkit import requests from django.conf import settings -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.db.models import QuerySet from django.urls import reverse @@ -394,7 +394,7 @@ def prepare_headers(access_token: str, application_uuid: uuid) -> dict: } -def get_application(id: uuid) -> Optional[Application]: +def get_application_for_ahjo(id: uuid) -> Optional[Application]: """Get the first accepted application.""" application = ( Application.objects.filter(pk=id, status=ApplicationStatus.ACCEPTED) @@ -402,11 +402,12 @@ def get_application(id: uuid) -> Optional[Application]: .first() ) if not application: - LOGGER.info("No applications found for Ahjo request.") - # Check that the handler has an ad_username set, if not, log an error and return None + raise ObjectDoesNotExist("No applications found for Ahjo request.") + # Check that the handler has an ad_username set, if not, ImproperlyConfigured if not application.calculation.handler.ad_username: - LOGGER.error("No ad_username set for the handler for Ahjo request.") - return None + raise ImproperlyConfigured( + "No ad_username set for the handler for Ahjo request." + ) return application @@ -449,14 +450,14 @@ def do_ahjo_request_with_json_payload( def open_case_in_ahjo(application_id: uuid): """Open a case in Ahjo.""" - application = get_application(application_id) - # if no suitable application is found, or the handler has no ad_id, bail out - if not application: - return - - ahjo_api_url = settings.AHJO_REST_API_URL - ahjo_token = get_token() - headers = prepare_headers(ahjo_token.access_token, application.id) - data = prepare_open_case_payload(application) - - do_ahjo_request_with_json_payload(ahjo_api_url, headers, data, application) + try: + application = get_application_for_ahjo(application_id) + ahjo_api_url = settings.AHJO_REST_API_URL + ahjo_token = get_token() + headers = prepare_headers(ahjo_token.access_token, application.id) + data = prepare_open_case_payload(application) + do_ahjo_request_with_json_payload(ahjo_api_url, headers, data, application) + except ObjectDoesNotExist as e: + LOGGER.error(f"Object not found: {e}") + except ImproperlyConfigured as e: + LOGGER.error(f"Improperly configured: {e}") diff --git a/backend/benefit/applications/tests/test_ahjo_integration.py b/backend/benefit/applications/tests/test_ahjo_integration.py index 7f67fb6da5..2ad185c4eb 100644 --- a/backend/benefit/applications/tests/test_ahjo_integration.py +++ b/backend/benefit/applications/tests/test_ahjo_integration.py @@ -6,6 +6,7 @@ from unittest.mock import patch import pytest +from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.http import FileResponse from django.urls import reverse @@ -24,6 +25,7 @@ generate_composed_files, generate_single_approved_file, generate_single_declined_file, + get_application_for_ahjo, REJECTED_TITLE, ) from applications.tests.factories import ApplicationFactory, DecidedApplicationFactory @@ -33,6 +35,7 @@ from helsinkibenefit.tests.conftest import * # noqa from shared.common.tests.utils import normalize_whitespace from shared.service_bus.enums import YtjOrganizationCode +from users.models import User DE_MINIMIS_AID_PARTIAL_TEXT = ( # In English ~= "support is granted as insignificant i.e. de minimis support" @@ -409,3 +412,24 @@ def test_ahjo_callback_unauthorized_ip_not_allowed( response = ahjo_client.post(url, **auth_headers) assert response.status_code == 403 + + +@pytest.mark.django_db +def test_get_application_for_ahjo_success(decided_application): + user = decided_application.calculation.handler + User.objects.filter(pk=user.id).update(ad_username="foobar") + assert get_application_for_ahjo(decided_application.id) == decided_application + + +@pytest.mark.django_db +def test_get_application_for_ahjo_no_application(): + # Try to get an application with a non-existing id + with pytest.raises(ObjectDoesNotExist): + get_application_for_ahjo(uuid.uuid4()) + + +@pytest.mark.django_db +def test_get_application_for_ahjo_no_ad_username(decided_application): + # Try to get an application with a handler that has no ad_username + with pytest.raises(ImproperlyConfigured): + get_application_for_ahjo(decided_application.id) From e1caf4eb82e917e50dc73d98f70deb3daf07b373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Riku=20Kestila=CC=88?= Date: Tue, 5 Dec 2023 15:31:34 +0200 Subject: [PATCH 09/11] fix: missing default value breaks tests in pipelines --- backend/benefit/helsinkibenefit/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/benefit/helsinkibenefit/settings.py b/backend/benefit/helsinkibenefit/settings.py index ac5ba0c56c..1c9100a9c9 100644 --- a/backend/benefit/helsinkibenefit/settings.py +++ b/backend/benefit/helsinkibenefit/settings.py @@ -161,7 +161,7 @@ # For AHJO Rest API authentication AHJO_CLIENT_ID=(str, ""), AHJO_CLIENT_SECRET=(str, ""), - AHJO_TOKEN_URL=(str, ""), + AHJO_TOKEN_URL=(str, "https://johdontyopoytahyte.hel.fi/ids4/connect/token"), AHJO_REST_API_URL=(str, "https://ahjohyte.hel.fi:9802/ahjorest/v1"), AHJO_REDIRECT_URL=(str, "https://helsinkilisa/dummyredirect.html"), AHJO_ALLOWED_IP=(str, ""), From 93e852b8605e00e16ba39276fde8589899608e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Riku=20Kestila=CC=88?= Date: Thu, 7 Dec 2023 10:15:24 +0200 Subject: [PATCH 10/11] fix: failing ahjo_connector test --- .../applications/tests/test_ahjo_authentication.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/benefit/applications/tests/test_ahjo_authentication.py b/backend/benefit/applications/tests/test_ahjo_authentication.py index 7ef8841b0b..89ba30d927 100644 --- a/backend/benefit/applications/tests/test_ahjo_authentication.py +++ b/backend/benefit/applications/tests/test_ahjo_authentication.py @@ -22,11 +22,11 @@ def token_response(): } -def test_is_configured(ahjo_connector, settings): - settings.AHJO_TOKEN_URL = "http://example.com/token" - settings.AHJO_CLIENT_ID = "client_id" - settings.AHJO_CLIENT_SECRET = "client_secret" - settings.AHJO_REDIRECT_URL = "http://example.com/redirect" +def test_is_configured(ahjo_connector): + ahjo_connector.AHJO_TOKEN_URL = "http://example.com/token" + ahjo_connector.AHJO_CLIENT_ID = "client_id" + ahjo_connector.AHJO_CLIENT_SECRET = "client_secret" + ahjo_connector.AHJO_REDIRECT_URL = "http://example.com/redirect" assert ahjo_connector.is_configured() is True # Test with missing config options From fa75ac5f2f0de26dfb52cc5c3f91472b421628e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Riku=20Kestila=CC=88?= Date: Thu, 7 Dec 2023 10:55:30 +0200 Subject: [PATCH 11/11] fix: strange fail on CI pipeline Sonarcloud test --- backend/benefit/applications/tests/factories.py | 2 +- backend/benefit/helsinkibenefit/settings.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/benefit/applications/tests/factories.py b/backend/benefit/applications/tests/factories.py index e4a1e97255..116d32c909 100755 --- a/backend/benefit/applications/tests/factories.py +++ b/backend/benefit/applications/tests/factories.py @@ -89,7 +89,7 @@ class ApplicationFactory(factory.django.DjangoModelFactory): alternative_company_postcode = factory.Faker("postcode", locale="fi_FI") company_bank_account_number = factory.Faker("iban", locale="fi_FI") company_contact_person_phone_number = factory.Sequence( - lambda n: f"050-10000{n}" + lambda n: f"045191{n}" ) # max.length in validation seems to be 10 digits company_contact_person_email = factory.Faker("email", locale="fi_FI") company_contact_person_first_name = factory.Faker("first_name", locale="fi_FI") diff --git a/backend/benefit/helsinkibenefit/settings.py b/backend/benefit/helsinkibenefit/settings.py index 1c9100a9c9..2ed11f658b 100644 --- a/backend/benefit/helsinkibenefit/settings.py +++ b/backend/benefit/helsinkibenefit/settings.py @@ -159,8 +159,8 @@ GDPR_API_QUERY_SCOPE=(str, "helsinkibenefit.gdprquery"), GDPR_API_DELETE_SCOPE=(str, "helsinkibenefit.gdprdelete"), # For AHJO Rest API authentication - AHJO_CLIENT_ID=(str, ""), - AHJO_CLIENT_SECRET=(str, ""), + AHJO_CLIENT_ID=(str, "foo"), + AHJO_CLIENT_SECRET=(str, "bar"), AHJO_TOKEN_URL=(str, "https://johdontyopoytahyte.hel.fi/ids4/connect/token"), AHJO_REST_API_URL=(str, "https://ahjohyte.hel.fi:9802/ahjorest/v1"), AHJO_REDIRECT_URL=(str, "https://helsinkilisa/dummyredirect.html"),