Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
from lcfs.web.api.charging_equipment.importer import _DuplicateSerialTracker


def test_duplicate_tracker_detects_file_duplicates():
def test_duplicate_tracker_detects_file_duplicates_same_site():
tracker = _DuplicateSerialTracker()
assert tracker.is_duplicate("ABC123") is False # first occurrence allowed
assert tracker.is_duplicate("ABC123", 1) is False # first occurrence allowed
assert tracker.summary_message() is None
assert tracker.is_duplicate("abc123") is True # case-insensitive duplicate
assert tracker.is_duplicate("abc123", 1) is True # case-insensitive duplicate at same site
assert (
tracker.summary_message()
== "1 record with duplicate serial numbers was not uploaded."
)


def test_duplicate_tracker_existing_serials_blocked():
tracker = _DuplicateSerialTracker(existing_serials={"SER-9"})
assert tracker.is_duplicate("SER-9") is True
def test_duplicate_tracker_allows_same_serial_different_site():
"""Same serial number at a different charging site should be allowed."""
tracker = _DuplicateSerialTracker()
assert tracker.is_duplicate("ABC123", 1) is False
assert tracker.is_duplicate("ABC123", 2) is False # different site → allowed
assert tracker.summary_message() is None


def test_duplicate_tracker_existing_serials_blocked_same_site():
tracker = _DuplicateSerialTracker(existing_serials=[("SER-9", 10)])
assert tracker.is_duplicate("SER-9", 10) is True # same site → blocked
assert (
tracker.summary_message()
== "1 record with duplicate serial numbers was not uploaded."
)
# Subsequent duplicates continue to increment
assert tracker.is_duplicate("ser-9") is True
assert tracker.is_duplicate("ser-9", 10) is True
assert (
tracker.summary_message()
== "2 records with duplicate serial numbers were not uploaded."
)


def test_duplicate_tracker_existing_serials_allowed_different_site():
"""Existing serial at a different site should not block the upload."""
tracker = _DuplicateSerialTracker(existing_serials=[("SER-9", 10)])
assert tracker.is_duplicate("SER-9", 20) is False # different site → allowed
assert tracker.summary_message() is None
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,18 @@ async def test_get_charging_equipment_by_id_not_found(repo, mock_db):

@pytest.mark.anyio
async def test_get_serial_numbers_for_organization(repo, mock_db):
"""Serial numbers for org should be normalized and deduplicated."""
"""Serial numbers for org should be normalized, paired with site_id, and deduplicated."""
mock_result = MagicMock()
mock_result.scalars.return_value.all.return_value = [
"SER-1",
" ser-2 ",
None,
mock_result.all.return_value = [
("SER-1", 10),
(" ser-2 ", 20),
(None, 30),
]
mock_db.execute.return_value = mock_result

serials = await repo.get_serial_numbers_for_organization(5)

assert serials == {"SER-1", "SER-2"}
assert serials == {("SER-1", 10), ("SER-2", 20)}
mock_db.execute.assert_called_once()


Expand Down
43 changes: 18 additions & 25 deletions backend/lcfs/tests/charging_site/test_charging_site_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -1225,19 +1225,15 @@ async def test_search_allocation_organizations_success(
mock_org2,
]

# Mock transaction partners
mock_repo.get_transaction_partners_from_allocation_agreements.return_value = [
"ABC Company", # Duplicate - should be filtered
# Mock all deduplicated names (sorted) from the consolidated repo method
mock_repo.get_allocating_organization_names.return_value = [
"ABC Company",
"ABC Corporation",
"ABC Historical C",
"ABC Partner A",
"ABC Partner B",
]

# Mock historical names
mock_repo.get_distinct_allocating_organization_names.return_value = [
"ABC Corporation", # Duplicate - should be filtered
"ABC Historical C",
]

result = await charging_site_service.search_allocation_organizations(1, "abc")

assert len(result) == 5 # 2 matched + 3 unmatched (duplicates removed)
Expand All @@ -1251,10 +1247,7 @@ async def test_search_allocation_organizations_success(
assert len(unmatched) == 3

mock_repo.get_allocation_agreement_organizations.assert_called_once_with(1)
mock_repo.get_transaction_partners_from_allocation_agreements.assert_called_once_with(
1
)
mock_repo.get_distinct_allocating_organization_names.assert_called_once_with(1)
mock_repo.get_allocating_organization_names.assert_called_once_with(1)

@pytest.mark.anyio
async def test_search_allocation_organizations_with_query_filter(
Expand All @@ -1273,11 +1266,11 @@ async def test_search_allocation_organizations_with_query_filter(
mock_org1,
mock_org2,
]
mock_repo.get_transaction_partners_from_allocation_agreements.return_value = [
"ABC Partner"
]
mock_repo.get_distinct_allocating_organization_names.return_value = [
"XYZ Historical"
mock_repo.get_allocating_organization_names.return_value = [
"ABC Company",
"ABC Partner",
"XYZ Corporation",
"XYZ Historical",
]

# Search for "abc" - should only return ABC entries
Expand All @@ -1296,11 +1289,10 @@ async def test_search_allocation_organizations_empty_query(
mock_org.name = "Test Org"

mock_repo.get_allocation_agreement_organizations.return_value = [mock_org]
mock_repo.get_transaction_partners_from_allocation_agreements.return_value = [
"Partner A"
]
mock_repo.get_distinct_allocating_organization_names.return_value = [
"Historical B"
mock_repo.get_allocating_organization_names.return_value = [
"Historical B",
"Partner A",
"Test Org",
]

result = await charging_site_service.search_allocation_organizations(1, "")
Expand All @@ -1321,8 +1313,9 @@ async def test_search_allocation_organizations_limits_results(
mock_orgs.append(mock_org)

mock_repo.get_allocation_agreement_organizations.return_value = mock_orgs
mock_repo.get_transaction_partners_from_allocation_agreements.return_value = []
mock_repo.get_distinct_allocating_organization_names.return_value = []
mock_repo.get_allocating_organization_names.return_value = [
f"Org {i:02d}" for i in range(60)
]

result = await charging_site_service.search_allocation_organizations(1, "org")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def mock_fse_repo():
repo = AsyncMock()
# get_fse_paginated returns a tuple (data, total_count)
repo.get_fse_paginated = AsyncMock(return_value=([], 0))
repo.get_fse_reporting_list_paginated = AsyncMock(return_value=([], 0))
repo.get_effective_fse_reporting_rows_for_export = AsyncMock(return_value=[])
return repo


Expand Down Expand Up @@ -101,11 +103,13 @@ def mock_annual_report():
report = Mock()
report.compliance_report_id = 1
report.compliance_report_group_uuid = "test-uuid"
report.organization_id = 1
report.version = 0
report.reporting_frequency = ReportingFrequency.ANNUAL

# Mock organization
organization = Mock()
organization.organization_id = 1
organization.name = "Test Organization"
report.organization = organization

Expand Down Expand Up @@ -433,6 +437,50 @@ async def test_load_fuel_supply_data_quarterly(
assert total_row[1] == "Total" # "Total" label in Fuel type column
assert len(total_row) == len(expected_headers)

@pytest.mark.anyio
async def test_export_uses_effective_fse_rows_for_bceid_users(
self,
compliance_report_exporter,
mock_annual_report,
):
exporter = compliance_report_exporter
exporter.cr_repo.get_compliance_report_by_id.return_value = mock_annual_report
exporter.summary_service.calculate_fuel_supply_compliance_units = AsyncMock(
return_value=1000
)
exporter.summary_service.calculate_fuel_export_compliance_units = AsyncMock(
return_value=-500
)

await exporter.export(1, is_government=False)

exporter.fse_repo.get_effective_fse_reporting_rows_for_export.assert_awaited_once_with(
organization_id=1,
compliance_report_id=1,
compliance_report_group_uuid="test-uuid",
)
exporter.fse_repo.get_fse_reporting_list_paginated.assert_not_called()

@pytest.mark.anyio
async def test_export_keeps_summary_fse_query_for_government_users(
self,
compliance_report_exporter,
mock_annual_report,
):
exporter = compliance_report_exporter
exporter.cr_repo.get_compliance_report_by_id.return_value = mock_annual_report
exporter.summary_service.calculate_fuel_supply_compliance_units = AsyncMock(
return_value=1000
)
exporter.summary_service.calculate_fuel_export_compliance_units = AsyncMock(
return_value=-500
)

await exporter.export(1, is_government=True)

exporter.fse_repo.get_fse_reporting_list_paginated.assert_called_once()
exporter.fse_repo.get_effective_fse_reporting_rows_for_export.assert_not_called()

@pytest.mark.anyio
async def test_load_notional_transfer_data_annual(
self,
Expand Down
Loading
Loading