From dc3543ac5f655c7f8ec9a76dad12cf014bf94621 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 19 May 2026 11:07:41 +0000 Subject: [PATCH 1/4] =?UTF-8?q?Coordination=20Hub=20fallback=20stores=20co?= =?UTF-8?q?rrect=20file=5Fsource=20in=20DB=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/db/models/uploaded_file.py | 1 + .../tests/test_pashub_service.py | 29 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/backend/app/db/models/uploaded_file.py b/backend/app/db/models/uploaded_file.py index f3cfee79..b6a73d5d 100644 --- a/backend/app/db/models/uploaded_file.py +++ b/backend/app/db/models/uploaded_file.py @@ -25,6 +25,7 @@ class FileTypeEnum(enum.Enum): class FileSourceEnum(enum.Enum): PAS_HUB = "pas hub" + COORDINATION_HUB = "coordination_hub" SHAREPOINT = "sharepoint" HUBSPOT = "hubspot" ECMK = "ecmk" diff --git a/backend/pashub_fetcher/tests/test_pashub_service.py b/backend/pashub_fetcher/tests/test_pashub_service.py index 991d2a46..1d6d167f 100644 --- a/backend/pashub_fetcher/tests/test_pashub_service.py +++ b/backend/pashub_fetcher/tests/test_pashub_service.py @@ -1,8 +1,9 @@ import pytest -from typing import Callable, Optional +from typing import Any, Callable, Optional from unittest.mock import MagicMock, call, patch +from backend.app.db.models.uploaded_file import FileSourceEnum from backend.pashub_fetcher.pashub_client import PashubClient, UnauthorizedError from backend.pashub_fetcher.pashub_service import PashubService from backend.pashub_fetcher.pashub_to_ara_trigger_request import ( @@ -306,6 +307,32 @@ def test_run_raises_unauthorized_when_both_clients_401() -> None: service.run(make_request()) +def test_run_persists_coordination_hub_file_source_when_pas_401_on_uprn_lookup() -> None: + pas_client = MagicMock(spec=PashubClient) + pas_client.get_uprn_by_job_id.side_effect = UnauthorizedError() + + coord_client = MagicMock(spec=PashubClient) + coord_client.get_uprn_by_job_id.return_value = "99999" + coord_client.get_core_evidence_files_by_job_id.return_value = ["/tmp/a.pdf"] + + factory = MagicMock(return_value=coord_client) + fake_session = MagicMock() + + service = make_service(pashub_client=pas_client, coordination_client_factory=factory) + + with ( + patch("backend.pashub_fetcher.pashub_service.upload_file_to_s3"), + patch("backend.pashub_fetcher.pashub_service.db_session") as mock_db, + patch("backend.pashub_fetcher.pashub_service.os.remove"), + ): + mock_db.return_value.__enter__.return_value = fake_session + service.run(make_request()) + + fake_session.add_all.assert_called_once() + added: list[Any] = fake_session.add_all.call_args[0][0] + assert added[0].file_source == FileSourceEnum.COORDINATION_HUB.value + + def test_run_warns_and_continues_when_site_notes_parsing_fails() -> None: mock_client = MagicMock(spec=PashubClient) mock_client.get_uprn_by_job_id.return_value = None From 1e115ba3dee7a63f9b2ebe4e56fb2f4a22da03f7 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 19 May 2026 11:09:01 +0000 Subject: [PATCH 2/4] =?UTF-8?q?Coordination=20Hub=20fallback=20stores=20co?= =?UTF-8?q?rrect=20file=5Fsource=20in=20DB=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/pashub_fetcher/pashub_service.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/backend/pashub_fetcher/pashub_service.py b/backend/pashub_fetcher/pashub_service.py index 13498a32..f7f6ccd9 100644 --- a/backend/pashub_fetcher/pashub_service.py +++ b/backend/pashub_fetcher/pashub_service.py @@ -60,9 +60,10 @@ class PashubService: else: try: uprn = active_client.get_uprn_by_job_id(job_id) - logger.info(f"Failed to access job {job_id} with PasHub credentials") except UnauthorizedError: - logger.info(f"Trying CoordinationHub credentials for job {job_id}") + logger.info( + f"PasHub credentials unauthorized for job {job_id}; retrying with CoordinationHub credentials" + ) active_client = self._get_coordination_client() uprn = active_client.get_uprn_by_job_id(job_id) @@ -85,8 +86,13 @@ class PashubService: if uprn or hubspot_deal_id: logger.info("Uploading files to s3") + file_source = ( + FileSourceEnum.PAS_HUB + if active_client is self._pashub_client + else FileSourceEnum.COORDINATION_HUB + ) upload_records = self._upload_to_s3_and_update_db( - job_files, uprn, hubspot_deal_id + job_files, uprn, hubspot_deal_id, file_source ) self._save_site_notes(upload_records) @@ -108,6 +114,7 @@ class PashubService: job_files: List[str], uprn: Optional[str], hubspot_deal_id: Optional[str], + file_source: FileSourceEnum, ) -> List[_FileUploadRecord]: if not uprn and not hubspot_deal_id: return [] @@ -133,7 +140,7 @@ class PashubService: s3_upload_timestamp=datetime.now(timezone.utc), uprn=int(uprn) if uprn else None, hubspot_deal_id=hubspot_deal_id, - file_source=FileSourceEnum.PAS_HUB.value, + file_source=file_source.value, file_type=get_file_type_string(filename), ) file_paths.append(file_path) From a4ad1ca11c90f8ff5e2080977b0567ab2ff8e269 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 19 May 2026 11:10:18 +0000 Subject: [PATCH 3/4] =?UTF-8?q?Coordination=20Hub=20file=20listing=20fallb?= =?UTF-8?q?ack=20stores=20correct=20file=5Fsource=20in=20DB=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/test_pashub_service.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/backend/pashub_fetcher/tests/test_pashub_service.py b/backend/pashub_fetcher/tests/test_pashub_service.py index 1d6d167f..cf1c489a 100644 --- a/backend/pashub_fetcher/tests/test_pashub_service.py +++ b/backend/pashub_fetcher/tests/test_pashub_service.py @@ -333,6 +333,31 @@ def test_run_persists_coordination_hub_file_source_when_pas_401_on_uprn_lookup() assert added[0].file_source == FileSourceEnum.COORDINATION_HUB.value +def test_run_persists_coordination_hub_file_source_when_pas_401_on_file_listing() -> None: + pas_client = MagicMock(spec=PashubClient) + pas_client.get_core_evidence_files_by_job_id.side_effect = UnauthorizedError() + + coord_client = MagicMock(spec=PashubClient) + coord_client.get_core_evidence_files_by_job_id.return_value = ["/tmp/a.pdf"] + + factory = MagicMock(return_value=coord_client) + fake_session = MagicMock() + + service = make_service(pashub_client=pas_client, coordination_client_factory=factory) + + with ( + patch("backend.pashub_fetcher.pashub_service.upload_file_to_s3"), + patch("backend.pashub_fetcher.pashub_service.db_session") as mock_db, + patch("backend.pashub_fetcher.pashub_service.os.remove"), + ): + mock_db.return_value.__enter__.return_value = fake_session + service.run(make_request(uprn="12345")) + + fake_session.add_all.assert_called_once() + added: list[Any] = fake_session.add_all.call_args[0][0] + assert added[0].file_source == FileSourceEnum.COORDINATION_HUB.value + + def test_run_warns_and_continues_when_site_notes_parsing_fails() -> None: mock_client = MagicMock(spec=PashubClient) mock_client.get_uprn_by_job_id.return_value = None From 20ad0616bcdc32eb24abee7bb05f4f707475e00b Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 19 May 2026 11:10:45 +0000 Subject: [PATCH 4/4] =?UTF-8?q?PAS=20Hub=20happy=20path=20asserts=20file?= =?UTF-8?q?=5Fsource=20"pas=20hub"=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/pashub_fetcher/tests/test_pashub_service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/pashub_fetcher/tests/test_pashub_service.py b/backend/pashub_fetcher/tests/test_pashub_service.py index cf1c489a..1f750117 100644 --- a/backend/pashub_fetcher/tests/test_pashub_service.py +++ b/backend/pashub_fetcher/tests/test_pashub_service.py @@ -148,10 +148,11 @@ def test_run_persists_uploaded_file_records_to_db() -> None: service.run(make_request(uprn="12345")) fake_session.add_all.assert_called_once() - added: list = fake_session.add_all.call_args[0][0] + added: list[Any] = fake_session.add_all.call_args[0][0] assert len(added) == 1 assert added[0].s3_file_bucket == "test-bucket" assert added[0].uprn == 12345 + assert added[0].file_source == FileSourceEnum.PAS_HUB.value # ---------------------------------------------------------------------------