From def9414192dd282513fda88c5fdf046f456ce863 Mon Sep 17 00:00:00 2001 From: Karim El Jazzar Date: Thu, 17 Sep 2026 14:09:15 -0700 Subject: [PATCH 1/3] 34788 - updated legal api to support tramways --- legal-api/src/legal_api/resources/v2/business/business.py | 6 ++++-- .../legal_api/resources/v2/business/business_comments.py | 4 +++- .../v2/business/business_filings/business_filings.py | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/legal-api/src/legal_api/resources/v2/business/business.py b/legal-api/src/legal_api/resources/v2/business/business.py index a5d1248400..59746b4052 100644 --- a/legal-api/src/legal_api/resources/v2/business/business.py +++ b/legal-api/src/legal_api/resources/v2/business/business.py @@ -15,6 +15,7 @@ Provides all the search and retrieval from the business entity datastore. """ +import re from contextlib import suppress from http import HTTPStatus @@ -63,7 +64,8 @@ def get_businesses(identifier: str): # - (i.e. business/person search updates) return get_businesses_public(identifier, True) - if identifier.startswith("T"): + # Temp bootstrap ids start with "T". Real tramways are TMY + 7 digits (e.g. TMY0000008). + if identifier.startswith("T") and not re.fullmatch(r"TMY\d{7}", identifier): return {"message": babel("No information on temp registrations.")}, 200 business = Business.find_by_identifier(identifier) @@ -135,7 +137,7 @@ def get_businesses(identifier: str): @jwt.requires_auth def get_businesses_public(identifier: str, slim = False): """Return a JSON object with public meta information about the business.""" - if identifier.startswith("T"): + if identifier.startswith("T") and not re.fullmatch(r"TMY\d{7}", identifier): return {"message": babel("No information on temp registrations.")}, 200 business = Business.find_by_identifier(identifier) diff --git a/legal-api/src/legal_api/resources/v2/business/business_comments.py b/legal-api/src/legal_api/resources/v2/business/business_comments.py index 03aad3a145..e679994de3 100644 --- a/legal-api/src/legal_api/resources/v2/business/business_comments.py +++ b/legal-api/src/legal_api/resources/v2/business/business_comments.py @@ -15,6 +15,7 @@ Provides all the search and retrieval from the business entity datastore. """ +import re from datetime import UTC, datetime from http import HTTPStatus @@ -38,7 +39,8 @@ def get_comments(identifier, comment_id=None): """Return a JSON object with meta information about the Service.""" # basic checks - if identifier.startswith("T"): + # Temp bootstrap ids start with "T". Real tramways are TMY + 7 digits (e.g. TMY0000008). + if identifier.startswith("T") and not re.fullmatch(r"TMY\d{7}", identifier): filing_model = FilingModel.get_temp_reg_filing(identifier) business = Business.find_by_internal_id(filing_model.business_id) else: diff --git a/legal-api/src/legal_api/resources/v2/business/business_filings/business_filings.py b/legal-api/src/legal_api/resources/v2/business/business_filings/business_filings.py index 8c951e83f0..d5187c20ab 100644 --- a/legal-api/src/legal_api/resources/v2/business/business_filings/business_filings.py +++ b/legal-api/src/legal_api/resources/v2/business/business_filings/business_filings.py @@ -99,7 +99,9 @@ class FilingModel(BaseModel, Generic[FilingT]): @pydantic_validate(query=QueryModel) def get_filings(identifier: str, filing_id: int | None = None): """Return a JSON object with meta information about the Filing Submission.""" - if filing_id or identifier.startswith("T"): + # Temp bootstrap ids start with "T" (e.g. Tabc12XyZ9). Real tramways are TMY + 7 digits + # (e.g. TMY0000008) — exclude only that shape so random temps like TMYHLpcaq7 stay temp. + if filing_id or (identifier.startswith("T") and not re.fullmatch(r"TMY\d{7}", identifier)): if str(request.args.get("public", None)).lower() == "true": return ListFilingResource.get_single_filing_public_json(filing_id) From 7ff1594d97d7c80535c3dc7ec8384c7a2443d40d Mon Sep 17 00:00:00 2001 From: Karim El Jazzar Date: Fri, 18 Sep 2026 12:49:28 -0700 Subject: [PATCH 2/3] added unit tests --- .../tests/unit/resources/v2/test_business.py | 28 +++++++++++++++ .../resources/v2/test_business_comments.py | 12 +++++++ .../v2/test_business_filings/test_filings.py | 35 +++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/legal-api/tests/unit/resources/v2/test_business.py b/legal-api/tests/unit/resources/v2/test_business.py index f43acb0fcd..5d39ea6bd9 100644 --- a/legal-api/tests/unit/resources/v2/test_business.py +++ b/legal-api/tests/unit/resources/v2/test_business.py @@ -213,6 +213,34 @@ def test_get_temp_business_info(session, client, jwt): headers=create_header(jwt, [STAFF_ROLE], identifier)) assert rv.status_code == HTTPStatus.OK + assert rv.json['message'] == 'No information on temp registrations.' + + +@pytest.mark.parametrize('identifier, is_temp_message', [ + ('TMYHLpcaq7', True), # random temp that happens to start with TMY + ('TMY0000008', False), # real tramway number +]) +def test_get_business_info_tramway_vs_temp_tmy_prefix(session, client, jwt, identifier, is_temp_message): + """Assert TMY+7digits is a real business; other TMY… temps stay temp.""" + if not is_temp_message: + factory_business_model(legal_name=f'{identifier} legal name', + identifier=identifier, + founding_date=datetime.fromtimestamp(0, UTC), + last_ledger_timestamp=datetime.fromtimestamp(0, UTC), + last_modified=datetime.fromtimestamp(0, UTC), + fiscal_year_end_date=None, + tax_id=None, + dissolution_date=None, + legal_type='TMY') + + rv = client.get('/api/v2/businesses/' + identifier, + headers=create_header(jwt, [STAFF_ROLE], identifier)) + + assert rv.status_code == HTTPStatus.OK + if is_temp_message: + assert rv.json['message'] == 'No information on temp registrations.' + else: + assert rv.json['business']['identifier'] == identifier @pytest.mark.parametrize('test_name,role,calls_auth', [ diff --git a/legal-api/tests/unit/resources/v2/test_business_comments.py b/legal-api/tests/unit/resources/v2/test_business_comments.py index 95b342709a..39530c47b6 100644 --- a/legal-api/tests/unit/resources/v2/test_business_comments.py +++ b/legal-api/tests/unit/resources/v2/test_business_comments.py @@ -47,6 +47,18 @@ def test_get_all_business_comments_no_results(session, client, jwt): assert 0 == len(rv.json.get('comments')) +def test_get_tramway_business_comments(session, client, jwt): + """Assert tramway identifiers use normal business lookup (not temp-reg path).""" + identifier = 'TMY0000008' + factory_business(identifier, entity_type='TMY') + + rv = client.get(f'/api/v2/businesses/{identifier}/comments', + headers=create_header(jwt, [STAFF_ROLE])) + + assert rv.status_code == HTTPStatus.OK + assert rv.json.get('comments') == [] + + def test_get_all_business_comments_only_one(session, client, jwt): """Assert that a list of comments with a single comment is returned correctly.""" identifier = 'CP7654321' diff --git a/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py b/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py index c710e95e58..8c2e659a0d 100644 --- a/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py +++ b/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py @@ -241,6 +241,41 @@ def test_get_temp_business_filing(session, client, jwt, legal_type, filing_type, assert rv.json['filing'][filing_type] == filing_json +def test_get_filings_tramway_uses_ledger_listing(session, client, jwt, mocker): + """Assert real tramway numbers use ledger listing, not the temp-reg path.""" + identifier = 'TMY0000008' + factory_business(identifier, entity_type='TMY') + mock_ledger = mocker.patch( + 'legal_api.resources.v2.business.business_filings.business_filings.ListFilingResource.get_ledger_listing', + return_value=({'filings': []}, HTTPStatus.OK)) + mock_single = mocker.patch( + 'legal_api.resources.v2.business.business_filings.business_filings.ListFilingResource.get_single_filing') + + rv = client.get(f'/api/v2/businesses/{identifier}/filings', + headers=create_header(jwt, [STAFF_ROLE], identifier)) + + assert rv.status_code == HTTPStatus.OK + mock_ledger.assert_called_once() + mock_single.assert_not_called() + + +def test_get_filings_temp_tmy_prefix_uses_single_filing(session, client, jwt, mocker): + """Assert temps that start with TMY still use the temp-reg filing path.""" + identifier = 'TMYHLpcaq7' + mock_ledger = mocker.patch( + 'legal_api.resources.v2.business.business_filings.business_filings.ListFilingResource.get_ledger_listing') + mock_single = mocker.patch( + 'legal_api.resources.v2.business.business_filings.business_filings.ListFilingResource.get_single_filing', + return_value=({'filing': {}}, HTTPStatus.OK)) + + rv = client.get(f'/api/v2/businesses/{identifier}/filings', + headers=create_header(jwt, [STAFF_ROLE], identifier)) + + assert rv.status_code == HTTPStatus.OK + mock_single.assert_called_once_with(identifier, None) + mock_ledger.assert_not_called() + + @pytest.mark.parametrize( 'jwt_role, expected', [ From 355494cfc0c95444cbb5cf4515080ca65cd8e916 Mon Sep 17 00:00:00 2001 From: Karim El Jazzar Date: Fri, 18 Sep 2026 13:14:06 -0700 Subject: [PATCH 3/3] fixed unit tests + SC issue --- .../tests/unit/resources/v2/test_business.py | 22 +++++++++++-------- .../resources/v2/test_business_comments.py | 10 ++++++--- .../v2/test_business_filings/test_filings.py | 6 ++++- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/legal-api/tests/unit/resources/v2/test_business.py b/legal-api/tests/unit/resources/v2/test_business.py index 5d39ea6bd9..571ab9501f 100644 --- a/legal-api/tests/unit/resources/v2/test_business.py +++ b/legal-api/tests/unit/resources/v2/test_business.py @@ -223,15 +223,19 @@ def test_get_temp_business_info(session, client, jwt): def test_get_business_info_tramway_vs_temp_tmy_prefix(session, client, jwt, identifier, is_temp_message): """Assert TMY+7digits is a real business; other TMY… temps stay temp.""" if not is_temp_message: - factory_business_model(legal_name=f'{identifier} legal name', - identifier=identifier, - founding_date=datetime.fromtimestamp(0, UTC), - last_ledger_timestamp=datetime.fromtimestamp(0, UTC), - last_modified=datetime.fromtimestamp(0, UTC), - fiscal_year_end_date=None, - tax_id=None, - dissolution_date=None, - legal_type='TMY') + # TMY identifiers don't pass the standard CP/BC/FM validate_identifier check. + # Set _identifier directly, same pattern as bootstrap temp_reg tests. + b = factory_business_model(legal_name=f'{identifier} legal name', + identifier='CP0000001', # valid placeholder to pass constructor + founding_date=datetime.fromtimestamp(0, UTC), + last_ledger_timestamp=datetime.fromtimestamp(0, UTC), + last_modified=datetime.fromtimestamp(0, UTC), + fiscal_year_end_date=None, + tax_id=None, + dissolution_date=None, + legal_type='TMY') + b._identifier = identifier + b.save() rv = client.get('/api/v2/businesses/' + identifier, headers=create_header(jwt, [STAFF_ROLE], identifier)) diff --git a/legal-api/tests/unit/resources/v2/test_business_comments.py b/legal-api/tests/unit/resources/v2/test_business_comments.py index 39530c47b6..20b3b0602f 100644 --- a/legal-api/tests/unit/resources/v2/test_business_comments.py +++ b/legal-api/tests/unit/resources/v2/test_business_comments.py @@ -50,13 +50,17 @@ def test_get_all_business_comments_no_results(session, client, jwt): def test_get_tramway_business_comments(session, client, jwt): """Assert tramway identifiers use normal business lookup (not temp-reg path).""" identifier = 'TMY0000008' - factory_business(identifier, entity_type='TMY') + # TMY identifiers don't pass the standard CP/BC/FM validate_identifier check. + # Use a valid placeholder then set _identifier directly. + b = factory_business('CP0000002', entity_type='TMY') + b._identifier = identifier + b.save() rv = client.get(f'/api/v2/businesses/{identifier}/comments', headers=create_header(jwt, [STAFF_ROLE])) - assert rv.status_code == HTTPStatus.OK - assert rv.json.get('comments') == [] + assert HTTPStatus.OK == rv.status_code + assert [] == rv.json.get('comments') def test_get_all_business_comments_only_one(session, client, jwt): diff --git a/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py b/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py index 8c2e659a0d..f1e38255f3 100644 --- a/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py +++ b/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py @@ -244,7 +244,11 @@ def test_get_temp_business_filing(session, client, jwt, legal_type, filing_type, def test_get_filings_tramway_uses_ledger_listing(session, client, jwt, mocker): """Assert real tramway numbers use ledger listing, not the temp-reg path.""" identifier = 'TMY0000008' - factory_business(identifier, entity_type='TMY') + # TMY identifiers don't pass the standard CP/BC/FM validate_identifier check. + # Use a valid placeholder then set _identifier directly. + b = factory_business('CP0000003', entity_type='TMY') + b._identifier = identifier + b.save() mock_ledger = mocker.patch( 'legal_api.resources.v2.business.business_filings.business_filings.ListFilingResource.get_ledger_listing', return_value=({'filings': []}, HTTPStatus.OK))