From 6f0e071a108f03a9032d469d154c3813ea00c3fc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 9 Jan 2026 10:19:42 +0000 Subject: [PATCH 1/7] Add collector for WealdenDistrictCouncil Closes #77 Generated with Codex CLI --- .../Councils/WealdenDistrictCouncil.cs | 248 ++++++++++++++++++ .../Councils/WealdenDistrictCouncilTests.cs | 37 +++ 2 files changed, 285 insertions(+) create mode 100644 BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs create mode 100644 BinDays.Api.IntegrationTests/Collectors/Councils/WealdenDistrictCouncilTests.cs diff --git a/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs b/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs new file mode 100644 index 00000000..dc93a1b9 --- /dev/null +++ b/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs @@ -0,0 +1,248 @@ +namespace BinDays.Api.Collectors.Collectors.Councils +{ + using BinDays.Api.Collectors.Collectors.Vendors; + using BinDays.Api.Collectors.Models; + using BinDays.Api.Collectors.Utilities; + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Text.Json; + + /// + /// Collector implementation for Wealden District Council. + /// + internal sealed partial class WealdenDistrictCouncil : GovUkCollectorBase, ICollector + { + /// + public string Name => "Wealden District Council"; + + /// + public Uri WebsiteUrl => new("https://www.wealden.gov.uk/"); + + /// + public override string GovUkId => "wealden"; + + /// + /// The list of bin types for this collector. + /// + private readonly IReadOnlyCollection _binTypes = new List() + { + new() + { + Name = "General Waste", + Colour = BinColour.Black, + Keys = new List() { "Refuse", "Rubbish" }.AsReadOnly(), + Type = BinType.Bin, + }, + new() + { + Name = "Recycling", + Colour = BinColour.Green, + Keys = new List() { "Recycling" }.AsReadOnly(), + Type = BinType.Bin, + }, + new() + { + Name = "Garden Waste", + Colour = BinColour.Brown, + Keys = new List() { "Garden" }.AsReadOnly(), + Type = BinType.Bin, + }, + }.AsReadOnly(); + + /// + public GetAddressesResponse GetAddresses(string postcode, ClientSideResponse? clientSideResponse) + { + var formattedPostcode = ProcessingUtilities.FormatPostcode(postcode) ?? string.Empty; + var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); + + // Prepare client-side request for getting cookies + if (clientSideResponse == null) + { + var clientSideRequest = new ClientSideRequest + { + RequestId = 1, + Url = "https://www.wealden.gov.uk/recycling-and-waste/bin-search/", + Method = "GET", + Headers = new() + { + { "User-Agent", Constants.UserAgent }, + }, + }; + + return new GetAddressesResponse + { + NextClientSideRequest = clientSideRequest + }; + } + // Prepare client-side request for getting addresses + else if (clientSideResponse.RequestId == 1) + { + var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( + clientSideResponse.Headers["set-cookie"]); + + var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() + { + { "action", "wealden_get_properties_in_postcode" }, + { "postcode", sanitizedPostcode } + }); + + var clientSideRequest = new ClientSideRequest + { + RequestId = 2, + Url = "https://www.wealden.gov.uk/wp-admin/admin-ajax.php", + Method = "POST", + Headers = new() + { + { "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" }, + { "X-Requested-With", "XMLHttpRequest" }, + { "cookie", requestCookies }, + { "User-Agent", Constants.UserAgent }, + }, + Body = requestBody, + }; + + return new GetAddressesResponse + { + NextClientSideRequest = clientSideRequest + }; + } + // Process addresses from response + else if (clientSideResponse.RequestId == 2) + { + using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); + var properties = jsonDoc.RootElement.GetProperty("properties").EnumerateArray(); + + var addresses = new List
(); + foreach (var propertyElement in properties) + { + var address = new Address + { + Property = propertyElement.GetProperty("address").GetString()!.Trim(), + Postcode = formattedPostcode, + Uid = propertyElement.GetProperty("uprn").GetString(), + }; + + addresses.Add(address); + } + + return new GetAddressesResponse + { + Addresses = addresses.AsReadOnly(), + }; + } + + throw new InvalidOperationException("Invalid client-side request."); + } + + /// + public GetBinDaysResponse GetBinDays(Address address, ClientSideResponse? clientSideResponse) + { + var formattedPostcode = ProcessingUtilities.FormatPostcode(address.Postcode ?? string.Empty) ?? string.Empty; + var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); + + // Prepare client-side request for getting cookies + if (clientSideResponse == null) + { + var requestUrl = $"https://www.wealden.gov.uk/recycling-and-waste/bin-search/?postcode={sanitizedPostcode}"; + + var clientSideRequest = new ClientSideRequest + { + RequestId = 1, + Url = requestUrl, + Method = "GET", + Headers = new() + { + { "User-Agent", Constants.UserAgent }, + }, + }; + + return new GetBinDaysResponse + { + NextClientSideRequest = clientSideRequest + }; + } + // Prepare client-side request for getting bin days + else if (clientSideResponse.RequestId == 1) + { + var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( + clientSideResponse.Headers["set-cookie"]); + + var cookies = string.IsNullOrWhiteSpace(requestCookies) + ? $"c_postcode={sanitizedPostcode}" + : $"{requestCookies}; c_postcode={sanitizedPostcode}"; + + var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() + { + { "action", "wealden_get_collections_for_uprn" }, + { "uprn", address.Uid! } + }); + + var clientSideRequest = new ClientSideRequest + { + RequestId = 2, + Url = "https://www.wealden.gov.uk/wp-admin/admin-ajax.php", + Method = "POST", + Headers = new() + { + { "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" }, + { "X-Requested-With", "XMLHttpRequest" }, + { "cookie", cookies }, + { "User-Agent", Constants.UserAgent }, + }, + Body = requestBody, + }; + + return new GetBinDaysResponse + { + NextClientSideRequest = clientSideRequest + }; + } + // Process bin days from response + else if (clientSideResponse.RequestId == 2) + { + using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); + var collection = jsonDoc.RootElement.GetProperty("collection"); + + var binDays = new List(); + + AddBinDay(collection, "refuseCollectionDate", "Refuse", address, binDays); + AddBinDay(collection, "recyclingCollectionDate", "Recycling", address, binDays); + AddBinDay(collection, "gardenCollectionDate", "Garden", address, binDays); + + return new GetBinDaysResponse + { + BinDays = ProcessingUtilities.ProcessBinDays(binDays), + }; + } + + throw new InvalidOperationException("Invalid client-side request."); + } + + private void AddBinDay(JsonElement collection, string propertyName, string service, Address address, List binDays) + { + if (!collection.TryGetProperty(propertyName, out var dateElement)) + { + return; + } + + var dateString = dateElement.GetString(); + + if (string.IsNullOrWhiteSpace(dateString)) + { + return; + } + + var date = DateOnly.ParseExact(dateString, "yyyy-MM-dd'T'HH:mm:ss", CultureInfo.InvariantCulture); + + var bins = ProcessingUtilities.GetMatchingBins(_binTypes, service); + + binDays.Add(new BinDay + { + Date = date, + Address = address, + Bins = bins + }); + } + } +} diff --git a/BinDays.Api.IntegrationTests/Collectors/Councils/WealdenDistrictCouncilTests.cs b/BinDays.Api.IntegrationTests/Collectors/Councils/WealdenDistrictCouncilTests.cs new file mode 100644 index 00000000..93f7630b --- /dev/null +++ b/BinDays.Api.IntegrationTests/Collectors/Councils/WealdenDistrictCouncilTests.cs @@ -0,0 +1,37 @@ +namespace BinDays.Api.IntegrationTests.Collectors.Councils +{ + using BinDays.Api.Collectors.Collectors; + using BinDays.Api.Collectors.Collectors.Councils; + using BinDays.Api.Collectors.Services; + using BinDays.Api.IntegrationTests.Helpers; + using System.Threading.Tasks; + using Xunit; + using Xunit.Abstractions; + + public class WealdenDistrictCouncilTests + { + private readonly IntegrationTestClient _client; + private static readonly ICollector _collector = new WealdenDistrictCouncil(); + private readonly CollectorService _collectorService = new([_collector]); + private readonly ITestOutputHelper _outputHelper; + + public WealdenDistrictCouncilTests(ITestOutputHelper outputHelper) + { + _outputHelper = outputHelper; + _client = new IntegrationTestClient(outputHelper); + } + + [Theory] + [InlineData("TN3 9PE")] + public async Task GetBinDaysTest(string postcode) + { + await TestSteps.EndToEnd( + _client, + _collectorService, + _collector, + postcode, + _outputHelper + ); + } + } +} From 54031b0729ee817cfc1a1cfaf58dac3424bc6ffe Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 9 Jan 2026 10:20:21 +0000 Subject: [PATCH 2/7] Auto-format code with dotnet format --- .../Councils/WealdenDistrictCouncil.cs | 496 +++++++++--------- .../Councils/WealdenDistrictCouncilTests.cs | 74 +-- 2 files changed, 285 insertions(+), 285 deletions(-) diff --git a/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs b/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs index dc93a1b9..8bd50bb1 100644 --- a/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs +++ b/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs @@ -1,248 +1,248 @@ -namespace BinDays.Api.Collectors.Collectors.Councils -{ - using BinDays.Api.Collectors.Collectors.Vendors; - using BinDays.Api.Collectors.Models; - using BinDays.Api.Collectors.Utilities; - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Text.Json; - - /// - /// Collector implementation for Wealden District Council. - /// - internal sealed partial class WealdenDistrictCouncil : GovUkCollectorBase, ICollector - { - /// - public string Name => "Wealden District Council"; - - /// - public Uri WebsiteUrl => new("https://www.wealden.gov.uk/"); - - /// - public override string GovUkId => "wealden"; - - /// - /// The list of bin types for this collector. - /// - private readonly IReadOnlyCollection _binTypes = new List() - { - new() - { - Name = "General Waste", - Colour = BinColour.Black, - Keys = new List() { "Refuse", "Rubbish" }.AsReadOnly(), - Type = BinType.Bin, - }, - new() - { - Name = "Recycling", - Colour = BinColour.Green, - Keys = new List() { "Recycling" }.AsReadOnly(), - Type = BinType.Bin, - }, - new() - { - Name = "Garden Waste", - Colour = BinColour.Brown, - Keys = new List() { "Garden" }.AsReadOnly(), - Type = BinType.Bin, - }, - }.AsReadOnly(); - - /// - public GetAddressesResponse GetAddresses(string postcode, ClientSideResponse? clientSideResponse) - { - var formattedPostcode = ProcessingUtilities.FormatPostcode(postcode) ?? string.Empty; - var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); - - // Prepare client-side request for getting cookies - if (clientSideResponse == null) - { - var clientSideRequest = new ClientSideRequest - { - RequestId = 1, - Url = "https://www.wealden.gov.uk/recycling-and-waste/bin-search/", - Method = "GET", - Headers = new() - { - { "User-Agent", Constants.UserAgent }, - }, - }; - - return new GetAddressesResponse - { - NextClientSideRequest = clientSideRequest - }; - } - // Prepare client-side request for getting addresses - else if (clientSideResponse.RequestId == 1) - { - var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( - clientSideResponse.Headers["set-cookie"]); - - var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() - { - { "action", "wealden_get_properties_in_postcode" }, - { "postcode", sanitizedPostcode } - }); - - var clientSideRequest = new ClientSideRequest - { - RequestId = 2, - Url = "https://www.wealden.gov.uk/wp-admin/admin-ajax.php", - Method = "POST", - Headers = new() - { - { "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" }, - { "X-Requested-With", "XMLHttpRequest" }, - { "cookie", requestCookies }, - { "User-Agent", Constants.UserAgent }, - }, - Body = requestBody, - }; - - return new GetAddressesResponse - { - NextClientSideRequest = clientSideRequest - }; - } - // Process addresses from response - else if (clientSideResponse.RequestId == 2) - { - using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); - var properties = jsonDoc.RootElement.GetProperty("properties").EnumerateArray(); - - var addresses = new List
(); - foreach (var propertyElement in properties) - { - var address = new Address - { - Property = propertyElement.GetProperty("address").GetString()!.Trim(), - Postcode = formattedPostcode, - Uid = propertyElement.GetProperty("uprn").GetString(), - }; - - addresses.Add(address); - } - - return new GetAddressesResponse - { - Addresses = addresses.AsReadOnly(), - }; - } - - throw new InvalidOperationException("Invalid client-side request."); - } - - /// - public GetBinDaysResponse GetBinDays(Address address, ClientSideResponse? clientSideResponse) - { - var formattedPostcode = ProcessingUtilities.FormatPostcode(address.Postcode ?? string.Empty) ?? string.Empty; - var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); - - // Prepare client-side request for getting cookies - if (clientSideResponse == null) - { - var requestUrl = $"https://www.wealden.gov.uk/recycling-and-waste/bin-search/?postcode={sanitizedPostcode}"; - - var clientSideRequest = new ClientSideRequest - { - RequestId = 1, - Url = requestUrl, - Method = "GET", - Headers = new() - { - { "User-Agent", Constants.UserAgent }, - }, - }; - - return new GetBinDaysResponse - { - NextClientSideRequest = clientSideRequest - }; - } - // Prepare client-side request for getting bin days - else if (clientSideResponse.RequestId == 1) - { - var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( - clientSideResponse.Headers["set-cookie"]); - - var cookies = string.IsNullOrWhiteSpace(requestCookies) - ? $"c_postcode={sanitizedPostcode}" - : $"{requestCookies}; c_postcode={sanitizedPostcode}"; - - var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() - { - { "action", "wealden_get_collections_for_uprn" }, - { "uprn", address.Uid! } - }); - - var clientSideRequest = new ClientSideRequest - { - RequestId = 2, - Url = "https://www.wealden.gov.uk/wp-admin/admin-ajax.php", - Method = "POST", - Headers = new() - { - { "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" }, - { "X-Requested-With", "XMLHttpRequest" }, - { "cookie", cookies }, - { "User-Agent", Constants.UserAgent }, - }, - Body = requestBody, - }; - - return new GetBinDaysResponse - { - NextClientSideRequest = clientSideRequest - }; - } - // Process bin days from response - else if (clientSideResponse.RequestId == 2) - { - using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); - var collection = jsonDoc.RootElement.GetProperty("collection"); - - var binDays = new List(); - - AddBinDay(collection, "refuseCollectionDate", "Refuse", address, binDays); - AddBinDay(collection, "recyclingCollectionDate", "Recycling", address, binDays); - AddBinDay(collection, "gardenCollectionDate", "Garden", address, binDays); - - return new GetBinDaysResponse - { - BinDays = ProcessingUtilities.ProcessBinDays(binDays), - }; - } - - throw new InvalidOperationException("Invalid client-side request."); - } - - private void AddBinDay(JsonElement collection, string propertyName, string service, Address address, List binDays) - { - if (!collection.TryGetProperty(propertyName, out var dateElement)) - { - return; - } - - var dateString = dateElement.GetString(); - - if (string.IsNullOrWhiteSpace(dateString)) - { - return; - } - - var date = DateOnly.ParseExact(dateString, "yyyy-MM-dd'T'HH:mm:ss", CultureInfo.InvariantCulture); - - var bins = ProcessingUtilities.GetMatchingBins(_binTypes, service); - - binDays.Add(new BinDay - { - Date = date, - Address = address, - Bins = bins - }); - } - } -} +namespace BinDays.Api.Collectors.Collectors.Councils +{ + using BinDays.Api.Collectors.Collectors.Vendors; + using BinDays.Api.Collectors.Models; + using BinDays.Api.Collectors.Utilities; + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Text.Json; + + /// + /// Collector implementation for Wealden District Council. + /// + internal sealed partial class WealdenDistrictCouncil : GovUkCollectorBase, ICollector + { + /// + public string Name => "Wealden District Council"; + + /// + public Uri WebsiteUrl => new("https://www.wealden.gov.uk/"); + + /// + public override string GovUkId => "wealden"; + + /// + /// The list of bin types for this collector. + /// + private readonly IReadOnlyCollection _binTypes = new List() + { + new() + { + Name = "General Waste", + Colour = BinColour.Black, + Keys = new List() { "Refuse", "Rubbish" }.AsReadOnly(), + Type = BinType.Bin, + }, + new() + { + Name = "Recycling", + Colour = BinColour.Green, + Keys = new List() { "Recycling" }.AsReadOnly(), + Type = BinType.Bin, + }, + new() + { + Name = "Garden Waste", + Colour = BinColour.Brown, + Keys = new List() { "Garden" }.AsReadOnly(), + Type = BinType.Bin, + }, + }.AsReadOnly(); + + /// + public GetAddressesResponse GetAddresses(string postcode, ClientSideResponse? clientSideResponse) + { + var formattedPostcode = ProcessingUtilities.FormatPostcode(postcode) ?? string.Empty; + var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); + + // Prepare client-side request for getting cookies + if (clientSideResponse == null) + { + var clientSideRequest = new ClientSideRequest + { + RequestId = 1, + Url = "https://www.wealden.gov.uk/recycling-and-waste/bin-search/", + Method = "GET", + Headers = new() + { + { "User-Agent", Constants.UserAgent }, + }, + }; + + return new GetAddressesResponse + { + NextClientSideRequest = clientSideRequest + }; + } + // Prepare client-side request for getting addresses + else if (clientSideResponse.RequestId == 1) + { + var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( + clientSideResponse.Headers["set-cookie"]); + + var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() + { + { "action", "wealden_get_properties_in_postcode" }, + { "postcode", sanitizedPostcode } + }); + + var clientSideRequest = new ClientSideRequest + { + RequestId = 2, + Url = "https://www.wealden.gov.uk/wp-admin/admin-ajax.php", + Method = "POST", + Headers = new() + { + { "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" }, + { "X-Requested-With", "XMLHttpRequest" }, + { "cookie", requestCookies }, + { "User-Agent", Constants.UserAgent }, + }, + Body = requestBody, + }; + + return new GetAddressesResponse + { + NextClientSideRequest = clientSideRequest + }; + } + // Process addresses from response + else if (clientSideResponse.RequestId == 2) + { + using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); + var properties = jsonDoc.RootElement.GetProperty("properties").EnumerateArray(); + + var addresses = new List
(); + foreach (var propertyElement in properties) + { + var address = new Address + { + Property = propertyElement.GetProperty("address").GetString()!.Trim(), + Postcode = formattedPostcode, + Uid = propertyElement.GetProperty("uprn").GetString(), + }; + + addresses.Add(address); + } + + return new GetAddressesResponse + { + Addresses = addresses.AsReadOnly(), + }; + } + + throw new InvalidOperationException("Invalid client-side request."); + } + + /// + public GetBinDaysResponse GetBinDays(Address address, ClientSideResponse? clientSideResponse) + { + var formattedPostcode = ProcessingUtilities.FormatPostcode(address.Postcode ?? string.Empty) ?? string.Empty; + var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); + + // Prepare client-side request for getting cookies + if (clientSideResponse == null) + { + var requestUrl = $"https://www.wealden.gov.uk/recycling-and-waste/bin-search/?postcode={sanitizedPostcode}"; + + var clientSideRequest = new ClientSideRequest + { + RequestId = 1, + Url = requestUrl, + Method = "GET", + Headers = new() + { + { "User-Agent", Constants.UserAgent }, + }, + }; + + return new GetBinDaysResponse + { + NextClientSideRequest = clientSideRequest + }; + } + // Prepare client-side request for getting bin days + else if (clientSideResponse.RequestId == 1) + { + var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( + clientSideResponse.Headers["set-cookie"]); + + var cookies = string.IsNullOrWhiteSpace(requestCookies) + ? $"c_postcode={sanitizedPostcode}" + : $"{requestCookies}; c_postcode={sanitizedPostcode}"; + + var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() + { + { "action", "wealden_get_collections_for_uprn" }, + { "uprn", address.Uid! } + }); + + var clientSideRequest = new ClientSideRequest + { + RequestId = 2, + Url = "https://www.wealden.gov.uk/wp-admin/admin-ajax.php", + Method = "POST", + Headers = new() + { + { "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" }, + { "X-Requested-With", "XMLHttpRequest" }, + { "cookie", cookies }, + { "User-Agent", Constants.UserAgent }, + }, + Body = requestBody, + }; + + return new GetBinDaysResponse + { + NextClientSideRequest = clientSideRequest + }; + } + // Process bin days from response + else if (clientSideResponse.RequestId == 2) + { + using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); + var collection = jsonDoc.RootElement.GetProperty("collection"); + + var binDays = new List(); + + AddBinDay(collection, "refuseCollectionDate", "Refuse", address, binDays); + AddBinDay(collection, "recyclingCollectionDate", "Recycling", address, binDays); + AddBinDay(collection, "gardenCollectionDate", "Garden", address, binDays); + + return new GetBinDaysResponse + { + BinDays = ProcessingUtilities.ProcessBinDays(binDays), + }; + } + + throw new InvalidOperationException("Invalid client-side request."); + } + + private void AddBinDay(JsonElement collection, string propertyName, string service, Address address, List binDays) + { + if (!collection.TryGetProperty(propertyName, out var dateElement)) + { + return; + } + + var dateString = dateElement.GetString(); + + if (string.IsNullOrWhiteSpace(dateString)) + { + return; + } + + var date = DateOnly.ParseExact(dateString, "yyyy-MM-dd'T'HH:mm:ss", CultureInfo.InvariantCulture); + + var bins = ProcessingUtilities.GetMatchingBins(_binTypes, service); + + binDays.Add(new BinDay + { + Date = date, + Address = address, + Bins = bins + }); + } + } +} diff --git a/BinDays.Api.IntegrationTests/Collectors/Councils/WealdenDistrictCouncilTests.cs b/BinDays.Api.IntegrationTests/Collectors/Councils/WealdenDistrictCouncilTests.cs index 93f7630b..52f95ccd 100644 --- a/BinDays.Api.IntegrationTests/Collectors/Councils/WealdenDistrictCouncilTests.cs +++ b/BinDays.Api.IntegrationTests/Collectors/Councils/WealdenDistrictCouncilTests.cs @@ -1,37 +1,37 @@ -namespace BinDays.Api.IntegrationTests.Collectors.Councils -{ - using BinDays.Api.Collectors.Collectors; - using BinDays.Api.Collectors.Collectors.Councils; - using BinDays.Api.Collectors.Services; - using BinDays.Api.IntegrationTests.Helpers; - using System.Threading.Tasks; - using Xunit; - using Xunit.Abstractions; - - public class WealdenDistrictCouncilTests - { - private readonly IntegrationTestClient _client; - private static readonly ICollector _collector = new WealdenDistrictCouncil(); - private readonly CollectorService _collectorService = new([_collector]); - private readonly ITestOutputHelper _outputHelper; - - public WealdenDistrictCouncilTests(ITestOutputHelper outputHelper) - { - _outputHelper = outputHelper; - _client = new IntegrationTestClient(outputHelper); - } - - [Theory] - [InlineData("TN3 9PE")] - public async Task GetBinDaysTest(string postcode) - { - await TestSteps.EndToEnd( - _client, - _collectorService, - _collector, - postcode, - _outputHelper - ); - } - } -} +namespace BinDays.Api.IntegrationTests.Collectors.Councils +{ + using BinDays.Api.Collectors.Collectors; + using BinDays.Api.Collectors.Collectors.Councils; + using BinDays.Api.Collectors.Services; + using BinDays.Api.IntegrationTests.Helpers; + using System.Threading.Tasks; + using Xunit; + using Xunit.Abstractions; + + public class WealdenDistrictCouncilTests + { + private readonly IntegrationTestClient _client; + private static readonly ICollector _collector = new WealdenDistrictCouncil(); + private readonly CollectorService _collectorService = new([_collector]); + private readonly ITestOutputHelper _outputHelper; + + public WealdenDistrictCouncilTests(ITestOutputHelper outputHelper) + { + _outputHelper = outputHelper; + _client = new IntegrationTestClient(outputHelper); + } + + [Theory] + [InlineData("TN3 9PE")] + public async Task GetBinDaysTest(string postcode) + { + await TestSteps.EndToEnd( + _client, + _collectorService, + _collector, + postcode, + _outputHelper + ); + } + } +} From 35dc0b9d9742ad85dd4889896f877c6d3a59f6e6 Mon Sep 17 00:00:00 2001 From: BadgerHobbs Date: Sat, 10 Jan 2026 01:05:03 +0000 Subject: [PATCH 3/7] Format WealdenDistrictCouncil --- .../Councils/WealdenDistrictCouncil.cs | 516 +++++++++--------- .../Councils/WealdenDistrictCouncilTests.cs | 61 +-- 2 files changed, 298 insertions(+), 279 deletions(-) diff --git a/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs b/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs index 8bd50bb1..93a27e90 100644 --- a/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs +++ b/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs @@ -1,248 +1,268 @@ -namespace BinDays.Api.Collectors.Collectors.Councils -{ - using BinDays.Api.Collectors.Collectors.Vendors; - using BinDays.Api.Collectors.Models; - using BinDays.Api.Collectors.Utilities; - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Text.Json; - - /// - /// Collector implementation for Wealden District Council. - /// - internal sealed partial class WealdenDistrictCouncil : GovUkCollectorBase, ICollector - { - /// - public string Name => "Wealden District Council"; - - /// - public Uri WebsiteUrl => new("https://www.wealden.gov.uk/"); - - /// - public override string GovUkId => "wealden"; - - /// - /// The list of bin types for this collector. - /// - private readonly IReadOnlyCollection _binTypes = new List() - { - new() - { - Name = "General Waste", - Colour = BinColour.Black, - Keys = new List() { "Refuse", "Rubbish" }.AsReadOnly(), - Type = BinType.Bin, - }, - new() - { - Name = "Recycling", - Colour = BinColour.Green, - Keys = new List() { "Recycling" }.AsReadOnly(), - Type = BinType.Bin, - }, - new() - { - Name = "Garden Waste", - Colour = BinColour.Brown, - Keys = new List() { "Garden" }.AsReadOnly(), - Type = BinType.Bin, - }, - }.AsReadOnly(); - - /// - public GetAddressesResponse GetAddresses(string postcode, ClientSideResponse? clientSideResponse) - { - var formattedPostcode = ProcessingUtilities.FormatPostcode(postcode) ?? string.Empty; - var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); - - // Prepare client-side request for getting cookies - if (clientSideResponse == null) - { - var clientSideRequest = new ClientSideRequest - { - RequestId = 1, - Url = "https://www.wealden.gov.uk/recycling-and-waste/bin-search/", - Method = "GET", - Headers = new() - { - { "User-Agent", Constants.UserAgent }, - }, - }; - - return new GetAddressesResponse - { - NextClientSideRequest = clientSideRequest - }; - } - // Prepare client-side request for getting addresses - else if (clientSideResponse.RequestId == 1) - { - var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( - clientSideResponse.Headers["set-cookie"]); - - var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() - { - { "action", "wealden_get_properties_in_postcode" }, - { "postcode", sanitizedPostcode } - }); - - var clientSideRequest = new ClientSideRequest - { - RequestId = 2, - Url = "https://www.wealden.gov.uk/wp-admin/admin-ajax.php", - Method = "POST", - Headers = new() - { - { "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" }, - { "X-Requested-With", "XMLHttpRequest" }, - { "cookie", requestCookies }, - { "User-Agent", Constants.UserAgent }, - }, - Body = requestBody, - }; - - return new GetAddressesResponse - { - NextClientSideRequest = clientSideRequest - }; - } - // Process addresses from response - else if (clientSideResponse.RequestId == 2) - { - using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); - var properties = jsonDoc.RootElement.GetProperty("properties").EnumerateArray(); - - var addresses = new List
(); - foreach (var propertyElement in properties) - { - var address = new Address - { - Property = propertyElement.GetProperty("address").GetString()!.Trim(), - Postcode = formattedPostcode, - Uid = propertyElement.GetProperty("uprn").GetString(), - }; - - addresses.Add(address); - } - - return new GetAddressesResponse - { - Addresses = addresses.AsReadOnly(), - }; - } - - throw new InvalidOperationException("Invalid client-side request."); - } - - /// - public GetBinDaysResponse GetBinDays(Address address, ClientSideResponse? clientSideResponse) - { - var formattedPostcode = ProcessingUtilities.FormatPostcode(address.Postcode ?? string.Empty) ?? string.Empty; - var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); - - // Prepare client-side request for getting cookies - if (clientSideResponse == null) - { - var requestUrl = $"https://www.wealden.gov.uk/recycling-and-waste/bin-search/?postcode={sanitizedPostcode}"; - - var clientSideRequest = new ClientSideRequest - { - RequestId = 1, - Url = requestUrl, - Method = "GET", - Headers = new() - { - { "User-Agent", Constants.UserAgent }, - }, - }; - - return new GetBinDaysResponse - { - NextClientSideRequest = clientSideRequest - }; - } - // Prepare client-side request for getting bin days - else if (clientSideResponse.RequestId == 1) - { - var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( - clientSideResponse.Headers["set-cookie"]); - - var cookies = string.IsNullOrWhiteSpace(requestCookies) - ? $"c_postcode={sanitizedPostcode}" - : $"{requestCookies}; c_postcode={sanitizedPostcode}"; - - var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() - { - { "action", "wealden_get_collections_for_uprn" }, - { "uprn", address.Uid! } - }); - - var clientSideRequest = new ClientSideRequest - { - RequestId = 2, - Url = "https://www.wealden.gov.uk/wp-admin/admin-ajax.php", - Method = "POST", - Headers = new() - { - { "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" }, - { "X-Requested-With", "XMLHttpRequest" }, - { "cookie", cookies }, - { "User-Agent", Constants.UserAgent }, - }, - Body = requestBody, - }; - - return new GetBinDaysResponse - { - NextClientSideRequest = clientSideRequest - }; - } - // Process bin days from response - else if (clientSideResponse.RequestId == 2) - { - using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); - var collection = jsonDoc.RootElement.GetProperty("collection"); - - var binDays = new List(); - - AddBinDay(collection, "refuseCollectionDate", "Refuse", address, binDays); - AddBinDay(collection, "recyclingCollectionDate", "Recycling", address, binDays); - AddBinDay(collection, "gardenCollectionDate", "Garden", address, binDays); - - return new GetBinDaysResponse - { - BinDays = ProcessingUtilities.ProcessBinDays(binDays), - }; - } - - throw new InvalidOperationException("Invalid client-side request."); - } - - private void AddBinDay(JsonElement collection, string propertyName, string service, Address address, List binDays) - { - if (!collection.TryGetProperty(propertyName, out var dateElement)) - { - return; - } - - var dateString = dateElement.GetString(); - - if (string.IsNullOrWhiteSpace(dateString)) - { - return; - } - - var date = DateOnly.ParseExact(dateString, "yyyy-MM-dd'T'HH:mm:ss", CultureInfo.InvariantCulture); - - var bins = ProcessingUtilities.GetMatchingBins(_binTypes, service); - - binDays.Add(new BinDay - { - Date = date, - Address = address, - Bins = bins - }); - } - } -} +namespace BinDays.Api.Collectors.Collectors.Councils; + +using BinDays.Api.Collectors.Collectors.Vendors; +using BinDays.Api.Collectors.Models; +using BinDays.Api.Collectors.Utilities; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.Json; + +/// +/// Collector implementation for Wealden District Council. +/// +internal sealed partial class WealdenDistrictCouncil : GovUkCollectorBase, ICollector +{ + /// + public string Name => "Wealden District Council"; + + /// + public Uri WebsiteUrl => new("https://www.wealden.gov.uk/"); + + /// + public override string GovUkId => "wealden"; + + /// + /// The list of bin types for this collector. + /// + private readonly IReadOnlyCollection _binTypes = + [ + new() + { + Name = "General Waste", + Colour = BinColour.Black, + Keys = [ "Refuse", "Rubbish" ], + Type = BinType.Bin, + }, + new() + { + Name = "Recycling", + Colour = BinColour.Green, + Keys = [ "Recycling" ], + Type = BinType.Bin, + }, + new() + { + Name = "Garden Waste", + Colour = BinColour.Brown, + Keys = [ "Garden" ], + Type = BinType.Bin, + }, + ]; + + /// + public GetAddressesResponse GetAddresses(string postcode, ClientSideResponse? clientSideResponse) + { + var formattedPostcode = ProcessingUtilities.FormatPostcode(postcode) ?? string.Empty; + var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); + + // Prepare client-side request for getting cookies + if (clientSideResponse == null) + { + var clientSideRequest = new ClientSideRequest + { + RequestId = 1, + Url = "https://www.wealden.gov.uk/recycling-and-waste/bin-search/", + Method = "GET", + Headers = new() + { + { "User-Agent", Constants.UserAgent }, + }, + }; + + var getAddressesResponse = new GetAddressesResponse + { + NextClientSideRequest = clientSideRequest, + }; + + return getAddressesResponse; + } + // Prepare client-side request for getting addresses + else if (clientSideResponse.RequestId == 1) + { + var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( + clientSideResponse.Headers["set-cookie"]); + + var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() + { + { "action", "wealden_get_properties_in_postcode" }, + { "postcode", sanitizedPostcode }, + }); + + var clientSideRequest = new ClientSideRequest + { + RequestId = 2, + Url = "https://www.wealden.gov.uk/wp-admin/admin-ajax.php", + Method = "POST", + Headers = new() + { + { "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" }, + { "X-Requested-With", "XMLHttpRequest" }, + { "cookie", requestCookies }, + { "User-Agent", Constants.UserAgent }, + }, + Body = requestBody, + }; + + var getAddressesResponse = new GetAddressesResponse + { + NextClientSideRequest = clientSideRequest, + }; + + return getAddressesResponse; + } + // Process addresses from response + else if (clientSideResponse.RequestId == 2) + { + using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); + var properties = jsonDoc.RootElement.GetProperty("properties").EnumerateArray(); + + var addresses = new List
(); + foreach (var propertyElement in properties) + { + var address = new Address + { + Property = propertyElement.GetProperty("address").GetString()!.Trim(), + Postcode = formattedPostcode, + Uid = propertyElement.GetProperty("uprn").GetString(), + }; + + addresses.Add(address); + } + + var getAddressesResponse = new GetAddressesResponse + { + Addresses = [.. addresses], + }; + + return getAddressesResponse; + } + + // Throw exception for invalid request + throw new InvalidOperationException("Invalid client-side request."); + } + + /// + public GetBinDaysResponse GetBinDays(Address address, ClientSideResponse? clientSideResponse) + { + var formattedPostcode = ProcessingUtilities.FormatPostcode(address.Postcode ?? string.Empty) ?? string.Empty; + var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); + + // Prepare client-side request for getting cookies + if (clientSideResponse == null) + { + var requestUrl = $"https://www.wealden.gov.uk/recycling-and-waste/bin-search/?postcode={sanitizedPostcode}"; + + var clientSideRequest = new ClientSideRequest + { + RequestId = 1, + Url = requestUrl, + Method = "GET", + Headers = new() + { + { "User-Agent", Constants.UserAgent }, + }, + }; + + var getBinDaysResponse = new GetBinDaysResponse + { + NextClientSideRequest = clientSideRequest, + }; + + return getBinDaysResponse; + } + // Prepare client-side request for getting bin days + else if (clientSideResponse.RequestId == 1) + { + var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( + clientSideResponse.Headers["set-cookie"]); + + var cookies = string.IsNullOrWhiteSpace(requestCookies) + ? $"c_postcode={sanitizedPostcode}" + : $"{requestCookies}; c_postcode={sanitizedPostcode}"; + + var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() + { + { "action", "wealden_get_collections_for_uprn" }, + { "uprn", address.Uid! }, + }); + + var clientSideRequest = new ClientSideRequest + { + RequestId = 2, + Url = "https://www.wealden.gov.uk/wp-admin/admin-ajax.php", + Method = "POST", + Headers = new() + { + { "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" }, + { "X-Requested-With", "XMLHttpRequest" }, + { "cookie", cookies }, + { "User-Agent", Constants.UserAgent }, + }, + Body = requestBody, + }; + + var getBinDaysResponse = new GetBinDaysResponse + { + NextClientSideRequest = clientSideRequest, + }; + + return getBinDaysResponse; + } + // Process bin days from response + else if (clientSideResponse.RequestId == 2) + { + using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); + var collection = jsonDoc.RootElement.GetProperty("collection"); + + var binDays = new List(); + + AddBinDay(collection, "refuseCollectionDate", "Refuse", address, binDays); + AddBinDay(collection, "recyclingCollectionDate", "Recycling", address, binDays); + AddBinDay(collection, "gardenCollectionDate", "Garden", address, binDays); + + var getBinDaysResponse = new GetBinDaysResponse + { + BinDays = ProcessingUtilities.ProcessBinDays(binDays), + }; + + return getBinDaysResponse; + } + + // Throw exception for invalid request + throw new InvalidOperationException("Invalid client-side request."); + } + + private void AddBinDay(JsonElement collection, string propertyName, string service, Address address, List binDays) + { + if (!collection.TryGetProperty(propertyName, out var dateElement)) + { + return; + } + + var dateString = dateElement.GetString(); + + if (string.IsNullOrWhiteSpace(dateString)) + { + return; + } + + var date = DateOnly.ParseExact( + dateString, + "yyyy-MM-dd'T'HH:mm:ss", + CultureInfo.InvariantCulture, + DateTimeStyles.None + ); + + var bins = ProcessingUtilities.GetMatchingBins(_binTypes, service); + + var binDay = new BinDay + { + Date = date, + Address = address, + Bins = bins, + }; + + binDays.Add(binDay); + } +} \ No newline at end of file diff --git a/BinDays.Api.IntegrationTests/Collectors/Councils/WealdenDistrictCouncilTests.cs b/BinDays.Api.IntegrationTests/Collectors/Councils/WealdenDistrictCouncilTests.cs index 52f95ccd..5bc39e2e 100644 --- a/BinDays.Api.IntegrationTests/Collectors/Councils/WealdenDistrictCouncilTests.cs +++ b/BinDays.Api.IntegrationTests/Collectors/Councils/WealdenDistrictCouncilTests.cs @@ -1,37 +1,36 @@ -namespace BinDays.Api.IntegrationTests.Collectors.Councils +namespace BinDays.Api.IntegrationTests.Collectors.Councils; + +using BinDays.Api.Collectors.Collectors; +using BinDays.Api.Collectors.Collectors.Councils; +using BinDays.Api.Collectors.Services; +using BinDays.Api.IntegrationTests.Helpers; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +public class WealdenDistrictCouncilTests { - using BinDays.Api.Collectors.Collectors; - using BinDays.Api.Collectors.Collectors.Councils; - using BinDays.Api.Collectors.Services; - using BinDays.Api.IntegrationTests.Helpers; - using System.Threading.Tasks; - using Xunit; - using Xunit.Abstractions; + private readonly IntegrationTestClient _client; + private static readonly ICollector _collector = new WealdenDistrictCouncil(); + private readonly CollectorService _collectorService = new([_collector]); + private readonly ITestOutputHelper _outputHelper; - public class WealdenDistrictCouncilTests + public WealdenDistrictCouncilTests(ITestOutputHelper outputHelper) { - private readonly IntegrationTestClient _client; - private static readonly ICollector _collector = new WealdenDistrictCouncil(); - private readonly CollectorService _collectorService = new([_collector]); - private readonly ITestOutputHelper _outputHelper; - - public WealdenDistrictCouncilTests(ITestOutputHelper outputHelper) - { - _outputHelper = outputHelper; - _client = new IntegrationTestClient(outputHelper); - } + _outputHelper = outputHelper; + _client = new IntegrationTestClient(outputHelper); + } - [Theory] - [InlineData("TN3 9PE")] - public async Task GetBinDaysTest(string postcode) - { - await TestSteps.EndToEnd( - _client, - _collectorService, - _collector, - postcode, - _outputHelper - ); - } + [Theory] + [InlineData("TN3 9PE")] + public async Task GetBinDaysTest(string postcode) + { + await TestSteps.EndToEnd( + _client, + _collectorService, + _collector, + postcode, + _outputHelper + ); } } From 46e7576757e60dd0e5017cac129386e5ce448829 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 10 Jan 2026 01:05:46 +0000 Subject: [PATCH 4/7] Auto-format code with dotnet format --- .../Councils/WealdenDistrictCouncil.cs | 536 +++++++++--------- 1 file changed, 268 insertions(+), 268 deletions(-) diff --git a/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs b/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs index 93a27e90..e076d016 100644 --- a/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs +++ b/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs @@ -1,268 +1,268 @@ -namespace BinDays.Api.Collectors.Collectors.Councils; - -using BinDays.Api.Collectors.Collectors.Vendors; -using BinDays.Api.Collectors.Models; -using BinDays.Api.Collectors.Utilities; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Text.Json; - -/// -/// Collector implementation for Wealden District Council. -/// -internal sealed partial class WealdenDistrictCouncil : GovUkCollectorBase, ICollector -{ - /// - public string Name => "Wealden District Council"; - - /// - public Uri WebsiteUrl => new("https://www.wealden.gov.uk/"); - - /// - public override string GovUkId => "wealden"; - - /// - /// The list of bin types for this collector. - /// - private readonly IReadOnlyCollection _binTypes = - [ - new() - { - Name = "General Waste", - Colour = BinColour.Black, - Keys = [ "Refuse", "Rubbish" ], - Type = BinType.Bin, - }, - new() - { - Name = "Recycling", - Colour = BinColour.Green, - Keys = [ "Recycling" ], - Type = BinType.Bin, - }, - new() - { - Name = "Garden Waste", - Colour = BinColour.Brown, - Keys = [ "Garden" ], - Type = BinType.Bin, - }, - ]; - - /// - public GetAddressesResponse GetAddresses(string postcode, ClientSideResponse? clientSideResponse) - { - var formattedPostcode = ProcessingUtilities.FormatPostcode(postcode) ?? string.Empty; - var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); - - // Prepare client-side request for getting cookies - if (clientSideResponse == null) - { - var clientSideRequest = new ClientSideRequest - { - RequestId = 1, - Url = "https://www.wealden.gov.uk/recycling-and-waste/bin-search/", - Method = "GET", - Headers = new() - { - { "User-Agent", Constants.UserAgent }, - }, - }; - - var getAddressesResponse = new GetAddressesResponse - { - NextClientSideRequest = clientSideRequest, - }; - - return getAddressesResponse; - } - // Prepare client-side request for getting addresses - else if (clientSideResponse.RequestId == 1) - { - var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( - clientSideResponse.Headers["set-cookie"]); - - var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() - { - { "action", "wealden_get_properties_in_postcode" }, - { "postcode", sanitizedPostcode }, - }); - - var clientSideRequest = new ClientSideRequest - { - RequestId = 2, - Url = "https://www.wealden.gov.uk/wp-admin/admin-ajax.php", - Method = "POST", - Headers = new() - { - { "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" }, - { "X-Requested-With", "XMLHttpRequest" }, - { "cookie", requestCookies }, - { "User-Agent", Constants.UserAgent }, - }, - Body = requestBody, - }; - - var getAddressesResponse = new GetAddressesResponse - { - NextClientSideRequest = clientSideRequest, - }; - - return getAddressesResponse; - } - // Process addresses from response - else if (clientSideResponse.RequestId == 2) - { - using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); - var properties = jsonDoc.RootElement.GetProperty("properties").EnumerateArray(); - - var addresses = new List
(); - foreach (var propertyElement in properties) - { - var address = new Address - { - Property = propertyElement.GetProperty("address").GetString()!.Trim(), - Postcode = formattedPostcode, - Uid = propertyElement.GetProperty("uprn").GetString(), - }; - - addresses.Add(address); - } - - var getAddressesResponse = new GetAddressesResponse - { - Addresses = [.. addresses], - }; - - return getAddressesResponse; - } - - // Throw exception for invalid request - throw new InvalidOperationException("Invalid client-side request."); - } - - /// - public GetBinDaysResponse GetBinDays(Address address, ClientSideResponse? clientSideResponse) - { - var formattedPostcode = ProcessingUtilities.FormatPostcode(address.Postcode ?? string.Empty) ?? string.Empty; - var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); - - // Prepare client-side request for getting cookies - if (clientSideResponse == null) - { - var requestUrl = $"https://www.wealden.gov.uk/recycling-and-waste/bin-search/?postcode={sanitizedPostcode}"; - - var clientSideRequest = new ClientSideRequest - { - RequestId = 1, - Url = requestUrl, - Method = "GET", - Headers = new() - { - { "User-Agent", Constants.UserAgent }, - }, - }; - - var getBinDaysResponse = new GetBinDaysResponse - { - NextClientSideRequest = clientSideRequest, - }; - - return getBinDaysResponse; - } - // Prepare client-side request for getting bin days - else if (clientSideResponse.RequestId == 1) - { - var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( - clientSideResponse.Headers["set-cookie"]); - - var cookies = string.IsNullOrWhiteSpace(requestCookies) - ? $"c_postcode={sanitizedPostcode}" - : $"{requestCookies}; c_postcode={sanitizedPostcode}"; - - var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() - { - { "action", "wealden_get_collections_for_uprn" }, - { "uprn", address.Uid! }, - }); - - var clientSideRequest = new ClientSideRequest - { - RequestId = 2, - Url = "https://www.wealden.gov.uk/wp-admin/admin-ajax.php", - Method = "POST", - Headers = new() - { - { "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" }, - { "X-Requested-With", "XMLHttpRequest" }, - { "cookie", cookies }, - { "User-Agent", Constants.UserAgent }, - }, - Body = requestBody, - }; - - var getBinDaysResponse = new GetBinDaysResponse - { - NextClientSideRequest = clientSideRequest, - }; - - return getBinDaysResponse; - } - // Process bin days from response - else if (clientSideResponse.RequestId == 2) - { - using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); - var collection = jsonDoc.RootElement.GetProperty("collection"); - - var binDays = new List(); - - AddBinDay(collection, "refuseCollectionDate", "Refuse", address, binDays); - AddBinDay(collection, "recyclingCollectionDate", "Recycling", address, binDays); - AddBinDay(collection, "gardenCollectionDate", "Garden", address, binDays); - - var getBinDaysResponse = new GetBinDaysResponse - { - BinDays = ProcessingUtilities.ProcessBinDays(binDays), - }; - - return getBinDaysResponse; - } - - // Throw exception for invalid request - throw new InvalidOperationException("Invalid client-side request."); - } - - private void AddBinDay(JsonElement collection, string propertyName, string service, Address address, List binDays) - { - if (!collection.TryGetProperty(propertyName, out var dateElement)) - { - return; - } - - var dateString = dateElement.GetString(); - - if (string.IsNullOrWhiteSpace(dateString)) - { - return; - } - - var date = DateOnly.ParseExact( - dateString, - "yyyy-MM-dd'T'HH:mm:ss", - CultureInfo.InvariantCulture, - DateTimeStyles.None - ); - - var bins = ProcessingUtilities.GetMatchingBins(_binTypes, service); - - var binDay = new BinDay - { - Date = date, - Address = address, - Bins = bins, - }; - - binDays.Add(binDay); - } -} \ No newline at end of file +namespace BinDays.Api.Collectors.Collectors.Councils; + +using BinDays.Api.Collectors.Collectors.Vendors; +using BinDays.Api.Collectors.Models; +using BinDays.Api.Collectors.Utilities; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.Json; + +/// +/// Collector implementation for Wealden District Council. +/// +internal sealed partial class WealdenDistrictCouncil : GovUkCollectorBase, ICollector +{ + /// + public string Name => "Wealden District Council"; + + /// + public Uri WebsiteUrl => new("https://www.wealden.gov.uk/"); + + /// + public override string GovUkId => "wealden"; + + /// + /// The list of bin types for this collector. + /// + private readonly IReadOnlyCollection _binTypes = + [ + new() + { + Name = "General Waste", + Colour = BinColour.Black, + Keys = [ "Refuse", "Rubbish" ], + Type = BinType.Bin, + }, + new() + { + Name = "Recycling", + Colour = BinColour.Green, + Keys = [ "Recycling" ], + Type = BinType.Bin, + }, + new() + { + Name = "Garden Waste", + Colour = BinColour.Brown, + Keys = [ "Garden" ], + Type = BinType.Bin, + }, + ]; + + /// + public GetAddressesResponse GetAddresses(string postcode, ClientSideResponse? clientSideResponse) + { + var formattedPostcode = ProcessingUtilities.FormatPostcode(postcode) ?? string.Empty; + var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); + + // Prepare client-side request for getting cookies + if (clientSideResponse == null) + { + var clientSideRequest = new ClientSideRequest + { + RequestId = 1, + Url = "https://www.wealden.gov.uk/recycling-and-waste/bin-search/", + Method = "GET", + Headers = new() + { + { "User-Agent", Constants.UserAgent }, + }, + }; + + var getAddressesResponse = new GetAddressesResponse + { + NextClientSideRequest = clientSideRequest, + }; + + return getAddressesResponse; + } + // Prepare client-side request for getting addresses + else if (clientSideResponse.RequestId == 1) + { + var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( + clientSideResponse.Headers["set-cookie"]); + + var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() + { + { "action", "wealden_get_properties_in_postcode" }, + { "postcode", sanitizedPostcode }, + }); + + var clientSideRequest = new ClientSideRequest + { + RequestId = 2, + Url = "https://www.wealden.gov.uk/wp-admin/admin-ajax.php", + Method = "POST", + Headers = new() + { + { "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" }, + { "X-Requested-With", "XMLHttpRequest" }, + { "cookie", requestCookies }, + { "User-Agent", Constants.UserAgent }, + }, + Body = requestBody, + }; + + var getAddressesResponse = new GetAddressesResponse + { + NextClientSideRequest = clientSideRequest, + }; + + return getAddressesResponse; + } + // Process addresses from response + else if (clientSideResponse.RequestId == 2) + { + using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); + var properties = jsonDoc.RootElement.GetProperty("properties").EnumerateArray(); + + var addresses = new List
(); + foreach (var propertyElement in properties) + { + var address = new Address + { + Property = propertyElement.GetProperty("address").GetString()!.Trim(), + Postcode = formattedPostcode, + Uid = propertyElement.GetProperty("uprn").GetString(), + }; + + addresses.Add(address); + } + + var getAddressesResponse = new GetAddressesResponse + { + Addresses = [.. addresses], + }; + + return getAddressesResponse; + } + + // Throw exception for invalid request + throw new InvalidOperationException("Invalid client-side request."); + } + + /// + public GetBinDaysResponse GetBinDays(Address address, ClientSideResponse? clientSideResponse) + { + var formattedPostcode = ProcessingUtilities.FormatPostcode(address.Postcode ?? string.Empty) ?? string.Empty; + var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); + + // Prepare client-side request for getting cookies + if (clientSideResponse == null) + { + var requestUrl = $"https://www.wealden.gov.uk/recycling-and-waste/bin-search/?postcode={sanitizedPostcode}"; + + var clientSideRequest = new ClientSideRequest + { + RequestId = 1, + Url = requestUrl, + Method = "GET", + Headers = new() + { + { "User-Agent", Constants.UserAgent }, + }, + }; + + var getBinDaysResponse = new GetBinDaysResponse + { + NextClientSideRequest = clientSideRequest, + }; + + return getBinDaysResponse; + } + // Prepare client-side request for getting bin days + else if (clientSideResponse.RequestId == 1) + { + var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( + clientSideResponse.Headers["set-cookie"]); + + var cookies = string.IsNullOrWhiteSpace(requestCookies) + ? $"c_postcode={sanitizedPostcode}" + : $"{requestCookies}; c_postcode={sanitizedPostcode}"; + + var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() + { + { "action", "wealden_get_collections_for_uprn" }, + { "uprn", address.Uid! }, + }); + + var clientSideRequest = new ClientSideRequest + { + RequestId = 2, + Url = "https://www.wealden.gov.uk/wp-admin/admin-ajax.php", + Method = "POST", + Headers = new() + { + { "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" }, + { "X-Requested-With", "XMLHttpRequest" }, + { "cookie", cookies }, + { "User-Agent", Constants.UserAgent }, + }, + Body = requestBody, + }; + + var getBinDaysResponse = new GetBinDaysResponse + { + NextClientSideRequest = clientSideRequest, + }; + + return getBinDaysResponse; + } + // Process bin days from response + else if (clientSideResponse.RequestId == 2) + { + using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); + var collection = jsonDoc.RootElement.GetProperty("collection"); + + var binDays = new List(); + + AddBinDay(collection, "refuseCollectionDate", "Refuse", address, binDays); + AddBinDay(collection, "recyclingCollectionDate", "Recycling", address, binDays); + AddBinDay(collection, "gardenCollectionDate", "Garden", address, binDays); + + var getBinDaysResponse = new GetBinDaysResponse + { + BinDays = ProcessingUtilities.ProcessBinDays(binDays), + }; + + return getBinDaysResponse; + } + + // Throw exception for invalid request + throw new InvalidOperationException("Invalid client-side request."); + } + + private void AddBinDay(JsonElement collection, string propertyName, string service, Address address, List binDays) + { + if (!collection.TryGetProperty(propertyName, out var dateElement)) + { + return; + } + + var dateString = dateElement.GetString(); + + if (string.IsNullOrWhiteSpace(dateString)) + { + return; + } + + var date = DateOnly.ParseExact( + dateString, + "yyyy-MM-dd'T'HH:mm:ss", + CultureInfo.InvariantCulture, + DateTimeStyles.None + ); + + var bins = ProcessingUtilities.GetMatchingBins(_binTypes, service); + + var binDay = new BinDay + { + Date = date, + Address = address, + Bins = bins, + }; + + binDays.Add(binDay); + } +} From 69629d11cbd5eb92ce1b89ece7716d6dc80c665f Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:55:09 +0000 Subject: [PATCH 5/7] Address PR comments and style guide compliance - Remove partial keyword (not using GeneratedRegex) - Fix unsafe dictionary access for set-cookie headers using TryGetValue - Remove unnecessary postcode formatting (already formatted by caller) - Add null-forgiving operator on GetString() for uprn - Add iteration comments per style guide - Replace AddBinDay helper method with inline foreach loop - Ensure all code follows style guide conventions Co-authored-by: Andrew Riggs --- .../Councils/WealdenDistrictCouncil.cs | 97 ++++++++++--------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs b/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs index e076d016..58a80d3c 100644 --- a/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs +++ b/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs @@ -11,7 +11,7 @@ namespace BinDays.Api.Collectors.Collectors.Councils; /// /// Collector implementation for Wealden District Council. /// -internal sealed partial class WealdenDistrictCouncil : GovUkCollectorBase, ICollector +internal sealed class WealdenDistrictCouncil : GovUkCollectorBase, ICollector { /// public string Name => "Wealden District Council"; @@ -53,8 +53,7 @@ internal sealed partial class WealdenDistrictCouncil : GovUkCollectorBase, IColl /// public GetAddressesResponse GetAddresses(string postcode, ClientSideResponse? clientSideResponse) { - var formattedPostcode = ProcessingUtilities.FormatPostcode(postcode) ?? string.Empty; - var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); + var sanitizedPostcode = postcode.Replace(" ", string.Empty); // Prepare client-side request for getting cookies if (clientSideResponse == null) @@ -80,8 +79,8 @@ public GetAddressesResponse GetAddresses(string postcode, ClientSideResponse? cl // Prepare client-side request for getting addresses else if (clientSideResponse.RequestId == 1) { - var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( - clientSideResponse.Headers["set-cookie"]); + clientSideResponse.Headers.TryGetValue("set-cookie", out var setCookieHeader); + var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie(setCookieHeader); var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() { @@ -117,14 +116,15 @@ public GetAddressesResponse GetAddresses(string postcode, ClientSideResponse? cl using var jsonDoc = JsonDocument.Parse(clientSideResponse.Content); var properties = jsonDoc.RootElement.GetProperty("properties").EnumerateArray(); + // Iterate through each property, and create a new address object var addresses = new List
(); foreach (var propertyElement in properties) { var address = new Address { Property = propertyElement.GetProperty("address").GetString()!.Trim(), - Postcode = formattedPostcode, - Uid = propertyElement.GetProperty("uprn").GetString(), + Postcode = postcode, + Uid = propertyElement.GetProperty("uprn").GetString()!, }; addresses.Add(address); @@ -145,8 +145,7 @@ public GetAddressesResponse GetAddresses(string postcode, ClientSideResponse? cl /// public GetBinDaysResponse GetBinDays(Address address, ClientSideResponse? clientSideResponse) { - var formattedPostcode = ProcessingUtilities.FormatPostcode(address.Postcode ?? string.Empty) ?? string.Empty; - var sanitizedPostcode = formattedPostcode.Replace(" ", string.Empty); + var sanitizedPostcode = (address.Postcode ?? string.Empty).Replace(" ", string.Empty); // Prepare client-side request for getting cookies if (clientSideResponse == null) @@ -174,8 +173,8 @@ public GetBinDaysResponse GetBinDays(Address address, ClientSideResponse? client // Prepare client-side request for getting bin days else if (clientSideResponse.RequestId == 1) { - var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( - clientSideResponse.Headers["set-cookie"]); + clientSideResponse.Headers.TryGetValue("set-cookie", out var setCookieHeader); + var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie(setCookieHeader); var cookies = string.IsNullOrWhiteSpace(requestCookies) ? $"c_postcode={sanitizedPostcode}" @@ -217,52 +216,56 @@ public GetBinDaysResponse GetBinDays(Address address, ClientSideResponse? client var binDays = new List(); - AddBinDay(collection, "refuseCollectionDate", "Refuse", address, binDays); - AddBinDay(collection, "recyclingCollectionDate", "Recycling", address, binDays); - AddBinDay(collection, "gardenCollectionDate", "Garden", address, binDays); - - var getBinDaysResponse = new GetBinDaysResponse + var binCollectionProperties = new Dictionary { - BinDays = ProcessingUtilities.ProcessBinDays(binDays), + { "refuseCollectionDate", "Refuse" }, + { "recyclingCollectionDate", "Recycling" }, + { "gardenCollectionDate", "Garden" }, }; - return getBinDaysResponse; - } + // Iterate through each bin collection property, and create a new bin day object + foreach (var property in binCollectionProperties) + { + if (!collection.TryGetProperty(property.Key, out var dateElement)) + { + continue; + } - // Throw exception for invalid request - throw new InvalidOperationException("Invalid client-side request."); - } + var dateString = dateElement.GetString(); - private void AddBinDay(JsonElement collection, string propertyName, string service, Address address, List binDays) - { - if (!collection.TryGetProperty(propertyName, out var dateElement)) - { - return; - } + if (string.IsNullOrWhiteSpace(dateString)) + { + continue; + } - var dateString = dateElement.GetString(); + var date = DateOnly.ParseExact( + dateString, + "yyyy-MM-dd'T'HH:mm:ss", + CultureInfo.InvariantCulture, + DateTimeStyles.None + ); - if (string.IsNullOrWhiteSpace(dateString)) - { - return; - } + var bins = ProcessingUtilities.GetMatchingBins(_binTypes, property.Value); - var date = DateOnly.ParseExact( - dateString, - "yyyy-MM-dd'T'HH:mm:ss", - CultureInfo.InvariantCulture, - DateTimeStyles.None - ); + var binDay = new BinDay + { + Date = date, + Address = address, + Bins = bins, + }; - var bins = ProcessingUtilities.GetMatchingBins(_binTypes, service); + binDays.Add(binDay); + } - var binDay = new BinDay - { - Date = date, - Address = address, - Bins = bins, - }; + var getBinDaysResponse = new GetBinDaysResponse + { + BinDays = ProcessingUtilities.ProcessBinDays(binDays), + }; - binDays.Add(binDay); + return getBinDaysResponse; + } + + // Throw exception for invalid request + throw new InvalidOperationException("Invalid client-side request."); } } From c4885c36287e76cc122944780dd6eb4320e33ea4 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:59:05 +0000 Subject: [PATCH 6/7] Add comments explaining postcode space removal The Wealden District Council API requires postcodes without spaces in form data, URL parameters, and cookies. Co-authored-by: Andrew Riggs --- .../Collectors/Councils/WealdenDistrictCouncil.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs b/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs index 58a80d3c..f5cc2fee 100644 --- a/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs +++ b/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs @@ -53,6 +53,7 @@ internal sealed class WealdenDistrictCouncil : GovUkCollectorBase, ICollector /// public GetAddressesResponse GetAddresses(string postcode, ClientSideResponse? clientSideResponse) { + // Remove spaces from postcode as the Wealden API requires postcodes without spaces in form data and URL parameters var sanitizedPostcode = postcode.Replace(" ", string.Empty); // Prepare client-side request for getting cookies @@ -145,6 +146,7 @@ public GetAddressesResponse GetAddresses(string postcode, ClientSideResponse? cl /// public GetBinDaysResponse GetBinDays(Address address, ClientSideResponse? clientSideResponse) { + // Remove spaces from postcode as the Wealden API requires postcodes without spaces in URL parameters and cookies var sanitizedPostcode = (address.Postcode ?? string.Empty).Replace(" ", string.Empty); // Prepare client-side request for getting cookies From 2b79cfb775467dac1a2ebf194e57eec77789f52d Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 23:01:52 +0000 Subject: [PATCH 7/7] Use direct header access with ! for set-cookie header Replace TryGetValue pattern with direct dictionary access and null-forgiving operator per style guide. The set-cookie header is required for the collector to function, so we should fail loudly if it's missing. Co-authored-by: Andrew Riggs --- .../Collectors/Councils/WealdenDistrictCouncil.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs b/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs index f5cc2fee..af3ac5c3 100644 --- a/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs +++ b/BinDays.Api.Collectors/Collectors/Councils/WealdenDistrictCouncil.cs @@ -80,8 +80,9 @@ public GetAddressesResponse GetAddresses(string postcode, ClientSideResponse? cl // Prepare client-side request for getting addresses else if (clientSideResponse.RequestId == 1) { - clientSideResponse.Headers.TryGetValue("set-cookie", out var setCookieHeader); - var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie(setCookieHeader); + var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( + clientSideResponse.Headers["set-cookie"]! + ); var requestBody = ProcessingUtilities.ConvertDictionaryToFormData(new() { @@ -175,8 +176,9 @@ public GetBinDaysResponse GetBinDays(Address address, ClientSideResponse? client // Prepare client-side request for getting bin days else if (clientSideResponse.RequestId == 1) { - clientSideResponse.Headers.TryGetValue("set-cookie", out var setCookieHeader); - var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie(setCookieHeader); + var requestCookies = ProcessingUtilities.ParseSetCookieHeaderForRequestCookie( + clientSideResponse.Headers["set-cookie"]! + ); var cookies = string.IsNullOrWhiteSpace(requestCookies) ? $"c_postcode={sanitizedPostcode}"