diff --git a/uk_bin_collection/tests/test_lancaster_city_council.py b/uk_bin_collection/tests/test_lancaster_city_council.py new file mode 100644 index 0000000000..b14ab93fcd --- /dev/null +++ b/uk_bin_collection/tests/test_lancaster_city_council.py @@ -0,0 +1,81 @@ +"""Unit tests for the Lancaster City Council scheduled-collections parser.""" + +from types import SimpleNamespace +from unittest.mock import MagicMock, patch + +from uk_bin_collection.uk_bin_collection.councils.LancasterCityCouncil import ( + CouncilClass, +) + +MODULE_PATH = "uk_bin_collection.uk_bin_collection.councils.LancasterCityCouncil" + +HOME_HTML = 'Go' +FORM_HTML = '
' +ADDRESS_HTML = '1 Test Street' + + +def collection(date: str, service: str) -> str: + """Return the portal markup for a single scheduled collection.""" + return f""" + + """ + + +def schedule_html(*items: tuple[str, str]) -> str: + """Return a scheduled-collections section containing the given collections.""" + body = "".join(collection(date, service) for date, service in items) + return f'
{body}
' + + +def parse_fixture(html: str): + """Run the parser with the portal requests mocked to return the given schedule.""" + session = MagicMock() + session.get.side_effect = [ + SimpleNamespace(text=HOME_HTML), + SimpleNamespace(text=FORM_HTML), + SimpleNamespace(text=html), + ] + session.post.return_value = SimpleNamespace(text=ADDRESS_HTML) + with patch(f"{MODULE_PATH}.requests.Session", return_value=session): + return CouncilClass().parse_data("", number="1", postcode="LA1 1RS") + + +def test_parse_data_keeps_recycling_streams_separate(): + """Red and Yellow recycling streams are reported as distinct bin types.""" + result = parse_fixture( + schedule_html( + ("29/10/2026", "Domestic Waste Collection Service"), + ("29/10/2026", "Food Waste Collection Service"), + ("05/11/2026", "Recycling Red Collection Service"), + ("12/11/2026", "Recycling Yellow Collection Service"), + ) + ) + + assert result == { + "bins": [ + {"type": "Domestic Waste", "collectionDate": "29/10/2026"}, + {"type": "Food Waste", "collectionDate": "29/10/2026"}, + {"type": "Recycling Red", "collectionDate": "05/11/2026"}, + {"type": "Recycling Yellow", "collectionDate": "12/11/2026"}, + ] + } + + +def test_parse_data_maps_unspecified_recycling_stream_to_recycling(): + """The "refer to calendar for stream" wording still maps to Recycling.""" + result = parse_fixture( + schedule_html( + ( + "05/11/2026", + "Recycling Collection - refer to calendar for stream", + ), + ) + ) + + assert result == { + "bins": [{"type": "Recycling", "collectionDate": "05/11/2026"}] + } diff --git a/uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py index 4d18915d3a..f56961a339 100644 --- a/uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py @@ -7,9 +7,12 @@ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass +# Specific recycling streams must precede the generic "Recycling" prefix. BIN_TYPES = ( "Domestic Waste", "Garden Waste", + "Recycling Red", + "Recycling Yellow", "Recycling", "Food Waste", ) @@ -28,6 +31,7 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: + """Scrape the Lancaster waste portal and return scheduled collections by bin type.""" # data to return data = {"bins": []}