From 9d630c83cf8181f6a68c47f9a12811642ce6badf Mon Sep 17 00:00:00 2001 From: moneebullah25 Date: Sat, 10 Oct 2026 10:49:03 -0400 Subject: [PATCH 1/3] fix(leads): skip missing name parts in converted account name --- backend/leads/services.py | 4 +++- .../test_convert_requires_account_access.py | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/backend/leads/services.py b/backend/leads/services.py index 391ce101..62b81a3c 100644 --- a/backend/leads/services.py +++ b/backend/leads/services.py @@ -22,7 +22,9 @@ def conversion_account_name(lead): """The name of the account a conversion files ``lead`` under.""" if lead.company_name: return lead.company_name - return f"{lead.first_name} {lead.last_name}" + return ( + " ".join(p for p in (lead.first_name, lead.last_name) if p) or f"Lead {lead.id}" + ) def conversion_refusal(request, lead): diff --git a/backend/leads/tests/test_convert_requires_account_access.py b/backend/leads/tests/test_convert_requires_account_access.py index f87e31ef..5abec9ea 100644 --- a/backend/leads/tests/test_convert_requires_account_access.py +++ b/backend/leads/tests/test_convert_requires_account_access.py @@ -25,7 +25,7 @@ from accounts.models import Account from common.models import Comment from contacts.models import Contact from leads.models import Lead -from leads.services import convert_lead_to_account +from leads.services import conversion_account_name, convert_lead_to_account from opportunity.models import Opportunity pytestmark = pytest.mark.django_db @@ -314,3 +314,19 @@ class TestAllowedWithAccountAccess: account = Account.objects.get(id=response.json()["account_id"]) assert account.name == "Elsewhere Inc" assert set(hidden_account.assigned_to.all()) == {admin_profile} + + +@pytest.mark.parametrize( + "first_name, last_name, expected", + [("Ada", None, "Ada"), (None, "Smith", "Smith"), ("Ada", "Smith", "Ada Smith")], +) +def test_account_name_skips_missing_name_parts(first_name, last_name, expected): + lead = SimpleNamespace( + company_name=None, first_name=first_name, last_name=last_name + ) + assert conversion_account_name(lead) == expected + + +def test_account_name_for_nameless_lead_has_no_none(): + lead = SimpleNamespace(id="abc", company_name=None, first_name=None, last_name=None) + assert conversion_account_name(lead) == "Lead abc" From fc875f8678ba6626ba8b6a7d3f4f24eb67ea18aa Mon Sep 17 00:00:00 2001 From: moneebullah25 Date: Sat, 10 Oct 2026 10:58:33 -0400 Subject: [PATCH 2/3] fix(leads): strip whitespace in conversion account name --- backend/leads/services.py | 7 +++---- .../tests/test_convert_requires_account_access.py | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/backend/leads/services.py b/backend/leads/services.py index 62b81a3c..3236330b 100644 --- a/backend/leads/services.py +++ b/backend/leads/services.py @@ -22,9 +22,8 @@ def conversion_account_name(lead): """The name of the account a conversion files ``lead`` under.""" if lead.company_name: return lead.company_name - return ( - " ".join(p for p in (lead.first_name, lead.last_name) if p) or f"Lead {lead.id}" - ) + parts = [(p or "").strip() for p in (lead.first_name, lead.last_name)] + return " ".join(p for p in parts if p) or f"Lead {lead.id}" def conversion_refusal(request, lead): @@ -35,7 +34,7 @@ def conversion_refusal(request, lead): open would make it theirs (and every contact linked to it). That is refused. An account they can open, or no match at all, is fine. - ``lead`` only needs ``company_name``, ``first_name`` and ``last_name``, so + ``lead`` only needs ``id``, ``company_name``, ``first_name`` and ``last_name``, so a view can ask with the values a save is about to write, before writing. """ matches = Account.objects.filter( diff --git a/backend/leads/tests/test_convert_requires_account_access.py b/backend/leads/tests/test_convert_requires_account_access.py index 5abec9ea..fc4528e0 100644 --- a/backend/leads/tests/test_convert_requires_account_access.py +++ b/backend/leads/tests/test_convert_requires_account_access.py @@ -318,11 +318,17 @@ class TestAllowedWithAccountAccess: @pytest.mark.parametrize( "first_name, last_name, expected", - [("Ada", None, "Ada"), (None, "Smith", "Smith"), ("Ada", "Smith", "Ada Smith")], + [ + ("Ada", None, "Ada"), + (None, "Smith", "Smith"), + ("Ada", "Smith", "Ada Smith"), + (" ", "Smith", "Smith"), + (" Ada ", " ", "Ada"), + ], ) def test_account_name_skips_missing_name_parts(first_name, last_name, expected): lead = SimpleNamespace( - company_name=None, first_name=first_name, last_name=last_name + id="abc", company_name=None, first_name=first_name, last_name=last_name ) assert conversion_account_name(lead) == expected @@ -330,3 +336,8 @@ def test_account_name_skips_missing_name_parts(first_name, last_name, expected): def test_account_name_for_nameless_lead_has_no_none(): lead = SimpleNamespace(id="abc", company_name=None, first_name=None, last_name=None) assert conversion_account_name(lead) == "Lead abc" + + +def test_account_name_for_whitespace_only_names_falls_back(): + lead = SimpleNamespace(id="abc", company_name=None, first_name=" ", last_name=" ") + assert conversion_account_name(lead) == "Lead abc" From e011f5281bdc5245f9113be88048dfc6bdb935ee Mon Sep 17 00:00:00 2001 From: moneebullah25 Date: Sat, 10 Oct 2026 11:06:11 -0400 Subject: [PATCH 3/3] test(leads): group account name tests in a class like the rest of the file --- .../test_convert_requires_account_access.py | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/backend/leads/tests/test_convert_requires_account_access.py b/backend/leads/tests/test_convert_requires_account_access.py index fc4528e0..d2e424c8 100644 --- a/backend/leads/tests/test_convert_requires_account_access.py +++ b/backend/leads/tests/test_convert_requires_account_access.py @@ -316,28 +316,33 @@ class TestAllowedWithAccountAccess: assert set(hidden_account.assigned_to.all()) == {admin_profile} -@pytest.mark.parametrize( - "first_name, last_name, expected", - [ - ("Ada", None, "Ada"), - (None, "Smith", "Smith"), - ("Ada", "Smith", "Ada Smith"), - (" ", "Smith", "Smith"), - (" Ada ", " ", "Ada"), - ], -) -def test_account_name_skips_missing_name_parts(first_name, last_name, expected): - lead = SimpleNamespace( - id="abc", company_name=None, first_name=first_name, last_name=last_name +class TestConversionAccountName: + @pytest.mark.parametrize( + "first_name, last_name, expected", + [ + ("Ada", None, "Ada"), + (None, "Smith", "Smith"), + ("Ada", "Smith", "Ada Smith"), + (" ", "Smith", "Smith"), + (" Ada ", " ", "Ada"), + ], ) - assert conversion_account_name(lead) == expected + def test_account_name_skips_missing_name_parts( + self, first_name, last_name, expected + ): + lead = SimpleNamespace( + id="abc", company_name=None, first_name=first_name, last_name=last_name + ) + assert conversion_account_name(lead) == expected + def test_account_name_for_nameless_lead_has_no_none(self): + lead = SimpleNamespace( + id="abc", company_name=None, first_name=None, last_name=None + ) + assert conversion_account_name(lead) == "Lead abc" -def test_account_name_for_nameless_lead_has_no_none(): - lead = SimpleNamespace(id="abc", company_name=None, first_name=None, last_name=None) - assert conversion_account_name(lead) == "Lead abc" - - -def test_account_name_for_whitespace_only_names_falls_back(): - lead = SimpleNamespace(id="abc", company_name=None, first_name=" ", last_name=" ") - assert conversion_account_name(lead) == "Lead abc" + def test_account_name_for_whitespace_only_names_falls_back(self): + lead = SimpleNamespace( + id="abc", company_name=None, first_name=" ", last_name=" " + ) + assert conversion_account_name(lead) == "Lead abc"