Skip to content
Open
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
81 changes: 81 additions & 0 deletions uk_bin_collection/tests/test_lancaster_city_council.py
Original file line number Diff line number Diff line change
@@ -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 = '<a href="https://lcc-wrp.whitespacews.com/mop.php?serviceID=A&seq=1">Go</a>'
FORM_HTML = '<form action="https://lcc-wrp.whitespacews.com/mop.php?seq=2"></form>'
ADDRESS_HTML = '<a href="mop.php?serviceID=A&seq=3&pIndex=1">1 Test Street</a>'


def collection(date: str, service: str) -> str:
"""Return the portal markup for a single scheduled collection."""
return f"""
<ul>
<li><img src="img/{service}.png" alt="Collection Item" /></li>
<li><p>{date}</p></li>
<li><p>{service}</p></li>
</ul>
"""


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'<section id="scheduled-collections">{body}</section>'


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"}]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
Expand All @@ -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": []}

Expand Down