uk_bin_collection 0.74.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. uk_bin_collection/README.rst +0 -0
  2. uk_bin_collection/tests/council_feature_input_parity.py +79 -0
  3. uk_bin_collection/tests/features/environment.py +7 -0
  4. uk_bin_collection/tests/features/validate_council_outputs.feature +767 -0
  5. uk_bin_collection/tests/input.json +1077 -0
  6. uk_bin_collection/tests/output.schema +41 -0
  7. uk_bin_collection/tests/step_defs/step_helpers/file_handler.py +46 -0
  8. uk_bin_collection/tests/step_defs/test_validate_council.py +87 -0
  9. uk_bin_collection/tests/test_collect_data.py +104 -0
  10. uk_bin_collection/tests/test_common_functions.py +342 -0
  11. uk_bin_collection/uk_bin_collection/collect_data.py +133 -0
  12. uk_bin_collection/uk_bin_collection/common.py +292 -0
  13. uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py +43 -0
  14. uk_bin_collection/uk_bin_collection/councils/ArunCouncil.py +97 -0
  15. uk_bin_collection/uk_bin_collection/councils/AylesburyValeCouncil.py +69 -0
  16. uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py +51 -0
  17. uk_bin_collection/uk_bin_collection/councils/BarnetCouncil.py +180 -0
  18. uk_bin_collection/uk_bin_collection/councils/BarnsleyMBCouncil.py +109 -0
  19. uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py +72 -0
  20. uk_bin_collection/uk_bin_collection/councils/BathAndNorthEastSomersetCouncil.py +100 -0
  21. uk_bin_collection/uk_bin_collection/councils/BedfordBoroughCouncil.py +49 -0
  22. uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py +70 -0
  23. uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py +147 -0
  24. uk_bin_collection/uk_bin_collection/councils/BirminghamCityCouncil.py +119 -0
  25. uk_bin_collection/uk_bin_collection/councils/BlackburnCouncil.py +105 -0
  26. uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py +104 -0
  27. uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py +103 -0
  28. uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py +137 -0
  29. uk_bin_collection/uk_bin_collection/councils/BristolCityCouncil.py +141 -0
  30. uk_bin_collection/uk_bin_collection/councils/BromleyBoroughCouncil.py +115 -0
  31. uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py +107 -0
  32. uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py +95 -0
  33. uk_bin_collection/uk_bin_collection/councils/BuryCouncil.py +65 -0
  34. uk_bin_collection/uk_bin_collection/councils/CalderdaleCouncil.py +123 -0
  35. uk_bin_collection/uk_bin_collection/councils/CannockChaseDistrictCouncil.py +65 -0
  36. uk_bin_collection/uk_bin_collection/councils/CardiffCouncil.py +172 -0
  37. uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py +96 -0
  38. uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py +54 -0
  39. uk_bin_collection/uk_bin_collection/councils/ChelmsfordCityCouncil.py +127 -0
  40. uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py +32 -0
  41. uk_bin_collection/uk_bin_collection/councils/CheshireWestAndChesterCouncil.py +125 -0
  42. uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py +134 -0
  43. uk_bin_collection/uk_bin_collection/councils/ConwyCountyBorough.py +27 -0
  44. uk_bin_collection/uk_bin_collection/councils/CrawleyBoroughCouncil.py +61 -0
  45. uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py +291 -0
  46. uk_bin_collection/uk_bin_collection/councils/DerbyshireDalesDistrictCouncil.py +100 -0
  47. uk_bin_collection/uk_bin_collection/councils/DoncasterCouncil.py +77 -0
  48. uk_bin_collection/uk_bin_collection/councils/DorsetCouncil.py +58 -0
  49. uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py +41 -0
  50. uk_bin_collection/uk_bin_collection/councils/DurhamCouncil.py +49 -0
  51. uk_bin_collection/uk_bin_collection/councils/EastCambridgeshireCouncil.py +44 -0
  52. uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py +74 -0
  53. uk_bin_collection/uk_bin_collection/councils/EastLindseyDistrictCouncil.py +108 -0
  54. uk_bin_collection/uk_bin_collection/councils/EastRidingCouncil.py +142 -0
  55. uk_bin_collection/uk_bin_collection/councils/EastSuffolkCouncil.py +112 -0
  56. uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py +70 -0
  57. uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py +48 -0
  58. uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py +61 -0
  59. uk_bin_collection/uk_bin_collection/councils/FenlandDistrictCouncil.py +65 -0
  60. uk_bin_collection/uk_bin_collection/councils/ForestOfDeanDistrictCouncil.py +113 -0
  61. uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py +118 -0
  62. uk_bin_collection/uk_bin_collection/councils/GedlingBoroughCouncil.py +1580 -0
  63. uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +55 -0
  64. uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py +150 -0
  65. uk_bin_collection/uk_bin_collection/councils/HaltonBoroughCouncil.py +142 -0
  66. uk_bin_collection/uk_bin_collection/councils/HaringeyCouncil.py +59 -0
  67. uk_bin_collection/uk_bin_collection/councils/HarrogateBoroughCouncil.py +63 -0
  68. uk_bin_collection/uk_bin_collection/councils/HighPeakCouncil.py +134 -0
  69. uk_bin_collection/uk_bin_collection/councils/HullCityCouncil.py +48 -0
  70. uk_bin_collection/uk_bin_collection/councils/HuntingdonDistrictCouncil.py +44 -0
  71. uk_bin_collection/uk_bin_collection/councils/KingstonUponThamesCouncil.py +84 -0
  72. uk_bin_collection/uk_bin_collection/councils/KirkleesCouncil.py +130 -0
  73. uk_bin_collection/uk_bin_collection/councils/KnowsleyMBCouncil.py +139 -0
  74. uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py +71 -0
  75. uk_bin_collection/uk_bin_collection/councils/LeedsCityCouncil.py +137 -0
  76. uk_bin_collection/uk_bin_collection/councils/LisburnCastlereaghCityCouncil.py +101 -0
  77. uk_bin_collection/uk_bin_collection/councils/LiverpoolCityCouncil.py +65 -0
  78. uk_bin_collection/uk_bin_collection/councils/LondonBoroughHounslow.py +82 -0
  79. uk_bin_collection/uk_bin_collection/councils/LondonBoroughRedbridge.py +161 -0
  80. uk_bin_collection/uk_bin_collection/councils/MaldonDistrictCouncil.py +52 -0
  81. uk_bin_collection/uk_bin_collection/councils/MalvernHillsDC.py +57 -0
  82. uk_bin_collection/uk_bin_collection/councils/ManchesterCityCouncil.py +106 -0
  83. uk_bin_collection/uk_bin_collection/councils/MansfieldDistrictCouncil.py +38 -0
  84. uk_bin_collection/uk_bin_collection/councils/MertonCouncil.py +58 -0
  85. uk_bin_collection/uk_bin_collection/councils/MidAndEastAntrimBoroughCouncil.py +128 -0
  86. uk_bin_collection/uk_bin_collection/councils/MidSussexDistrictCouncil.py +80 -0
  87. uk_bin_collection/uk_bin_collection/councils/MiltonKeynesCityCouncil.py +54 -0
  88. uk_bin_collection/uk_bin_collection/councils/MoleValleyDistrictCouncil.py +98 -0
  89. uk_bin_collection/uk_bin_collection/councils/NeathPortTalbotCouncil.py +139 -0
  90. uk_bin_collection/uk_bin_collection/councils/NewarkAndSherwoodDC.py +52 -0
  91. uk_bin_collection/uk_bin_collection/councils/NewcastleCityCouncil.py +57 -0
  92. uk_bin_collection/uk_bin_collection/councils/NewhamCouncil.py +58 -0
  93. uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py +203 -0
  94. uk_bin_collection/uk_bin_collection/councils/NorthEastDerbyshireDistrictCouncil.py +115 -0
  95. uk_bin_collection/uk_bin_collection/councils/NorthEastLincs.py +53 -0
  96. uk_bin_collection/uk_bin_collection/councils/NorthKestevenDistrictCouncil.py +45 -0
  97. uk_bin_collection/uk_bin_collection/councils/NorthLanarkshireCouncil.py +46 -0
  98. uk_bin_collection/uk_bin_collection/councils/NorthLincolnshireCouncil.py +58 -0
  99. uk_bin_collection/uk_bin_collection/councils/NorthNorfolkDistrictCouncil.py +108 -0
  100. uk_bin_collection/uk_bin_collection/councils/NorthNorthamptonshireCouncil.py +72 -0
  101. uk_bin_collection/uk_bin_collection/councils/NorthSomersetCouncil.py +76 -0
  102. uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py +220 -0
  103. uk_bin_collection/uk_bin_collection/councils/NorthWestLeicestershire.py +114 -0
  104. uk_bin_collection/uk_bin_collection/councils/NorthYorkshire.py +58 -0
  105. uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +123 -0
  106. uk_bin_collection/uk_bin_collection/councils/NottinghamCityCouncil.py +36 -0
  107. uk_bin_collection/uk_bin_collection/councils/OldhamCouncil.py +51 -0
  108. uk_bin_collection/uk_bin_collection/councils/PortsmouthCityCouncil.py +131 -0
  109. uk_bin_collection/uk_bin_collection/councils/PrestonCityCouncil.py +97 -0
  110. uk_bin_collection/uk_bin_collection/councils/ReadingBoroughCouncil.py +30 -0
  111. uk_bin_collection/uk_bin_collection/councils/ReigateAndBansteadBoroughCouncil.py +81 -0
  112. uk_bin_collection/uk_bin_collection/councils/RenfrewshireCouncil.py +135 -0
  113. uk_bin_collection/uk_bin_collection/councils/RhonddaCynonTaffCouncil.py +80 -0
  114. uk_bin_collection/uk_bin_collection/councils/RochdaleCouncil.py +69 -0
  115. uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py +60 -0
  116. uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py +93 -0
  117. uk_bin_collection/uk_bin_collection/councils/RushcliffeBoroughCouncil.py +100 -0
  118. uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py +81 -0
  119. uk_bin_collection/uk_bin_collection/councils/SalfordCityCouncil.py +70 -0
  120. uk_bin_collection/uk_bin_collection/councils/SevenoaksDistrictCouncil.py +106 -0
  121. uk_bin_collection/uk_bin_collection/councils/SheffieldCityCouncil.py +54 -0
  122. uk_bin_collection/uk_bin_collection/councils/ShropshireCouncil.py +45 -0
  123. uk_bin_collection/uk_bin_collection/councils/SolihullCouncil.py +48 -0
  124. uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py +203 -0
  125. uk_bin_collection/uk_bin_collection/councils/SouthAyrshireCouncil.py +73 -0
  126. uk_bin_collection/uk_bin_collection/councils/SouthCambridgeshireCouncil.py +65 -0
  127. uk_bin_collection/uk_bin_collection/councils/SouthGloucestershireCouncil.py +74 -0
  128. uk_bin_collection/uk_bin_collection/councils/SouthLanarkshireCouncil.py +78 -0
  129. uk_bin_collection/uk_bin_collection/councils/SouthNorfolkCouncil.py +91 -0
  130. uk_bin_collection/uk_bin_collection/councils/SouthOxfordshireCouncil.py +93 -0
  131. uk_bin_collection/uk_bin_collection/councils/SouthTynesideCouncil.py +98 -0
  132. uk_bin_collection/uk_bin_collection/councils/StAlbansCityAndDistrictCouncil.py +43 -0
  133. uk_bin_collection/uk_bin_collection/councils/StHelensBC.py +56 -0
  134. uk_bin_collection/uk_bin_collection/councils/StaffordshireMoorlandsDistrictCouncil.py +112 -0
  135. uk_bin_collection/uk_bin_collection/councils/StockportBoroughCouncil.py +39 -0
  136. uk_bin_collection/uk_bin_collection/councils/StokeOnTrentCityCouncil.py +79 -0
  137. uk_bin_collection/uk_bin_collection/councils/StratfordUponAvonCouncil.py +94 -0
  138. uk_bin_collection/uk_bin_collection/councils/SunderlandCityCouncil.py +100 -0
  139. uk_bin_collection/uk_bin_collection/councils/SwaleBoroughCouncil.py +52 -0
  140. uk_bin_collection/uk_bin_collection/councils/TamesideMBCouncil.py +62 -0
  141. uk_bin_collection/uk_bin_collection/councils/TandridgeDistrictCouncil.py +60 -0
  142. uk_bin_collection/uk_bin_collection/councils/TelfordAndWrekinCouncil.py +50 -0
  143. uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py +203 -0
  144. uk_bin_collection/uk_bin_collection/councils/TonbridgeAndMallingBC.py +101 -0
  145. uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py +51 -0
  146. uk_bin_collection/uk_bin_collection/councils/TorridgeDistrictCouncil.py +154 -0
  147. uk_bin_collection/uk_bin_collection/councils/ValeofGlamorganCouncil.py +119 -0
  148. uk_bin_collection/uk_bin_collection/councils/ValeofWhiteHorseCouncil.py +103 -0
  149. uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py +89 -0
  150. uk_bin_collection/uk_bin_collection/councils/WarwickDistrictCouncil.py +34 -0
  151. uk_bin_collection/uk_bin_collection/councils/WaverleyBoroughCouncil.py +119 -0
  152. uk_bin_collection/uk_bin_collection/councils/WealdenDistrictCouncil.py +86 -0
  153. uk_bin_collection/uk_bin_collection/councils/WelhatCouncil.py +73 -0
  154. uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py +134 -0
  155. uk_bin_collection/uk_bin_collection/councils/WestLindseyDistrictCouncil.py +118 -0
  156. uk_bin_collection/uk_bin_collection/councils/WestLothianCouncil.py +103 -0
  157. uk_bin_collection/uk_bin_collection/councils/WestNorthamptonshireCouncil.py +34 -0
  158. uk_bin_collection/uk_bin_collection/councils/WestSuffolkCouncil.py +64 -0
  159. uk_bin_collection/uk_bin_collection/councils/WiganBoroughCouncil.py +97 -0
  160. uk_bin_collection/uk_bin_collection/councils/WiltshireCouncil.py +135 -0
  161. uk_bin_collection/uk_bin_collection/councils/WindsorAndMaidenheadCouncil.py +134 -0
  162. uk_bin_collection/uk_bin_collection/councils/WokingBoroughCouncil.py +114 -0
  163. uk_bin_collection/uk_bin_collection/councils/WyreCouncil.py +89 -0
  164. uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py +45 -0
  165. uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py +33 -0
  166. uk_bin_collection/uk_bin_collection/get_bin_data.py +165 -0
  167. uk_bin_collection-0.74.0.dist-info/LICENSE +21 -0
  168. uk_bin_collection-0.74.0.dist-info/METADATA +247 -0
  169. uk_bin_collection-0.74.0.dist-info/RECORD +171 -0
  170. uk_bin_collection-0.74.0.dist-info/WHEEL +4 -0
  171. uk_bin_collection-0.74.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,41 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-06/schema#",
3
+ "$ref": "#/definitions/BinData",
4
+ "definitions": {
5
+ "BinData": {
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "bins": {
10
+ "type": "array",
11
+ "items": {
12
+ "$ref": "#/definitions/Bin"
13
+ },
14
+ "minItems": 1
15
+ }
16
+ },
17
+ "required": [
18
+ "bins"
19
+ ],
20
+ "title": "BinData"
21
+ },
22
+ "Bin": {
23
+ "type": "object",
24
+ "additionalProperties": false,
25
+ "properties": {
26
+ "type": {
27
+ "type": "string"
28
+ },
29
+ "collectionDate": {
30
+ "type": "string",
31
+ "pattern": "\\d{2}/\\d{2}/\\d{4}"
32
+ }
33
+ },
34
+ "required": [
35
+ "collectionDate",
36
+ "type"
37
+ ],
38
+ "title": "Bin"
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,46 @@
1
+ import json
2
+ import logging
3
+ from jsonschema import validate, ValidationError
4
+ from pathlib import Path
5
+ from typing import Any, Dict
6
+
7
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s")
8
+
9
+ # Dynamically compute the base path relative to this file's location
10
+ current_file_path = Path(__file__).resolve()
11
+ BASE_PATH = current_file_path.parent.parent.parent.parent / "tests"
12
+
13
+
14
+ def load_json_file(file_name: str) -> Dict[str, Any]:
15
+ file_path = BASE_PATH / file_name
16
+ try:
17
+ with open(file_path, "r") as f:
18
+ data = json.load(f)
19
+ logging.info(f"{file_name} file successfully loaded")
20
+ return data
21
+ except UnicodeDecodeError as e:
22
+ logging.error(f"Failed to load {file_name}: {e}")
23
+ raise
24
+ except json.JSONDecodeError as e:
25
+ logging.error(f"Failed to parse JSON in {file_name}: {e}")
26
+ raise
27
+
28
+
29
+ def validate_json(json_str: str) -> Dict[str, Any]:
30
+ try:
31
+ return json.loads(json_str)
32
+ except json.JSONDecodeError as err:
33
+ logging.error(f"JSON validation error: {err}")
34
+ raise
35
+
36
+
37
+ def validate_json_schema(json_str: str, schema: Dict[str, Any]) -> bool:
38
+ json_data = validate_json(json_str)
39
+ try:
40
+ validate(instance=json_data, schema=schema)
41
+ except ValidationError as err:
42
+ logging.error(f"Schema validation error: {err}")
43
+ logging.info(f"Data: {json_str}")
44
+ logging.info(f"Schema: {schema}")
45
+ raise
46
+ return True
@@ -0,0 +1,87 @@
1
+ import logging
2
+ import traceback
3
+ from typing import Any, Generator, Callable
4
+
5
+ import pytest
6
+ from pytest_bdd import scenario, given, when, then, parsers
7
+ from functools import wraps
8
+
9
+ from step_helpers import file_handler
10
+ from uk_bin_collection.uk_bin_collection import collect_data
11
+
12
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s")
13
+
14
+ @scenario("../features/validate_council_outputs.feature", "Validate Council Output")
15
+ def test_scenario_outline() -> None:
16
+ pass
17
+
18
+ def handle_test_errors(func: Callable[..., Any]) -> Callable[..., Any]:
19
+ @wraps(func)
20
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
21
+ try:
22
+ return func(*args, **kwargs)
23
+ except Exception as e:
24
+ logging.error(f"Error in test '{func.__name__}': {e}")
25
+ logging.error(traceback.format_exc())
26
+ raise e
27
+ return wrapper
28
+
29
+ @pytest.fixture
30
+ @handle_test_errors
31
+ def context() -> Generator[Any, None, None]:
32
+ class Context:
33
+ metadata: dict[str, Any]
34
+ council: str
35
+ parse_result: Any
36
+
37
+ return Context()
38
+
39
+ @handle_test_errors
40
+ @given(parsers.parse("the council: {council_name}"))
41
+ def get_council_step(context: Any, council_name: str) -> None:
42
+ council_input_data = file_handler.load_json_file("input.json")
43
+ context.metadata = council_input_data[council_name]
44
+
45
+ @handle_test_errors
46
+ @when(parsers.parse("we scrape the data from {council}"))
47
+ def scrape_step(context: Any, council: str, headless_mode: str, local_browser: str, selenium_url: str) -> None:
48
+ context.council = council
49
+
50
+ args = [council, context.metadata["url"]]
51
+
52
+ if "uprn" in context.metadata:
53
+ uprn = context.metadata["uprn"]
54
+ args.append(f"-u={uprn}")
55
+ if "postcode" in context.metadata:
56
+ postcode = context.metadata["postcode"]
57
+ args.append(f"-p={postcode}")
58
+ if "house_number" in context.metadata:
59
+ house_number = context.metadata["house_number"]
60
+ args.append(f"-n={house_number}")
61
+ if "usrn" in context.metadata:
62
+ usrn = context.metadata["usrn"]
63
+ args.append(f"-us={usrn}")
64
+ if headless_mode == "True":
65
+ args.append("--headless")
66
+ else:
67
+ args.append("--not-headless")
68
+
69
+ if local_browser == "False":
70
+ args.append(f"-w={selenium_url}")
71
+ if "skip_get_url" in context.metadata:
72
+ args.append("-s")
73
+
74
+ CollectData = collect_data.UKBinCollectionApp()
75
+ CollectData.set_args(args)
76
+ context.parse_result = CollectData.run()
77
+
78
+ @handle_test_errors
79
+ @then("the result is valid json")
80
+ def validate_json_step(context: Any) -> None:
81
+ assert file_handler.validate_json(context.parse_result), "Invalid JSON output"
82
+
83
+ @handle_test_errors
84
+ @then("the output should validate against the schema")
85
+ def validate_output_step(context: Any) -> None:
86
+ council_schema = file_handler.load_json_file("output.schema")
87
+ assert file_handler.validate_json_schema(context.parse_result, council_schema), "Schema validation failed"
@@ -0,0 +1,104 @@
1
+ import json
2
+ from unittest import mock
3
+
4
+ import pytest
5
+ from requests import exceptions as req_exp
6
+ from requests.models import Response
7
+ from uk_bin_collection.get_bin_data import AbstractGetBinDataClass as agbdc
8
+ from uk_bin_collection.get_bin_data import setup_logging
9
+ import logging
10
+
11
+
12
+ def mocked_requests_get(*args, **kwargs):
13
+ class MockResponse:
14
+ def __init__(self, json_data, status_code, raise_error_type):
15
+ self.text = json_data
16
+ self.status_code = status_code
17
+ if raise_error_type is not None:
18
+ self.raise_for_status = self.raise_error(raise_error_type)
19
+ else:
20
+ self.raise_for_status = lambda: None
21
+
22
+ def raise_error(self, errorType):
23
+ if errorType == "HTTPError":
24
+ raise req_exp.HTTPError()
25
+ elif errorType == "ConnectionError":
26
+ raise req_exp.ConnectionError()
27
+ elif errorType == "Timeout":
28
+ raise req_exp.Timeout()
29
+ elif errorType == "RequestException":
30
+ raise req_exp.RequestException()
31
+ return errorType
32
+
33
+ if args[0] == "aurl":
34
+ return MockResponse({"test_data": "test"}, 200, None)
35
+ elif args[0] == "HTTPError":
36
+ return MockResponse({}, 999, "HTTPError")
37
+ elif args[0] == "ConnectionError":
38
+ return MockResponse({}, 999, "ConnectionError")
39
+ elif args[0] == "Timeout":
40
+ return MockResponse({}, 999, "Timeout")
41
+ elif args[0] == "RequestException":
42
+ return MockResponse({}, 999, "RequestException")
43
+ elif args[0] == "notPage":
44
+ return MockResponse("not json", 200, None)
45
+ return MockResponse(None, 404, "HTTPError")
46
+
47
+
48
+ # Unit tests
49
+
50
+
51
+ def test_logging_exception():
52
+ logging_dict = "SW1A 1AA"
53
+ with pytest.raises(ValueError) as exc_info:
54
+ result = setup_logging(logging_dict, "ROOT")
55
+ assert exc_info.typename == "ValueError"
56
+
57
+
58
+ def test_setup_logging_valid_config():
59
+ # Example of a minimal valid logging configuration dictionary
60
+ logging_config = {
61
+ "version": 1,
62
+ "handlers": {
63
+ "console": {
64
+ "class": "logging.StreamHandler",
65
+ "level": "DEBUG",
66
+ },
67
+ },
68
+ "loggers": {
69
+ "ROOT": {
70
+ "handlers": ["console"],
71
+ "level": "DEBUG",
72
+ },
73
+ },
74
+ }
75
+ logger_name = "ROOT"
76
+ # Run the function with valid logging configuration
77
+ logger = setup_logging(logging_config, logger_name)
78
+
79
+ # Assert that logger is correctly configured
80
+ assert logger.name == logger_name
81
+ assert logger.level == logging.DEBUG
82
+
83
+
84
+ @mock.patch("requests.get", side_effect=mocked_requests_get)
85
+ def test_get_data(mock_get):
86
+ page_data = agbdc.get_data("aurl")
87
+ assert page_data.text == {"test_data": "test"}
88
+
89
+
90
+ @pytest.mark.parametrize(
91
+ "url", ["HTTPError", "ConnectionError", "Timeout", "RequestException"]
92
+ )
93
+ @mock.patch("requests.get", side_effect=mocked_requests_get)
94
+ def test_get_data_error(mock_get, url):
95
+ with pytest.raises(Exception) as exc_info:
96
+ result = agbdc.get_data(url)
97
+ assert exc_info.typename == url
98
+
99
+
100
+ def test_output_json():
101
+ bin_data = {"bin": ""}
102
+ output = agbdc.output_json(bin_data)
103
+ assert type(output) == str
104
+ assert output == '{\n "bin": ""\n}'
@@ -0,0 +1,342 @@
1
+ from unittest import mock
2
+ import pytest
3
+ from uk_bin_collection.common import *
4
+ from io import StringIO
5
+ from contextlib import redirect_stdout
6
+ from unittest.mock import patch, MagicMock, mock_open
7
+ from selenium.common.exceptions import WebDriverException
8
+ from urllib3.exceptions import MaxRetryError
9
+
10
+
11
+ def test_check_postcode_valid():
12
+ valid_postcode = "SW1A 1AA"
13
+ result = check_postcode(valid_postcode)
14
+ assert result is True
15
+
16
+
17
+ def test_check_postcode_invalid():
18
+ invalid_postcode = "BADPOSTCODE"
19
+ with pytest.raises(ValueError) as exc_info:
20
+ result = check_postcode(invalid_postcode)
21
+ assert exc_info._excinfo[1].args[0] == "Exception: Invalid postcode Status: 404"
22
+ assert exc_info.type == ValueError
23
+
24
+
25
+ def test_check_paon():
26
+ valid_house_num = "1"
27
+ result = check_paon(valid_house_num)
28
+ assert result is True
29
+
30
+
31
+ def test_check_paon_invalid(capfd):
32
+ invalid_house_num = None
33
+ with pytest.raises(SystemExit) as exc_info:
34
+ result = check_paon(invalid_house_num)
35
+ out, err = capfd.readouterr()
36
+ assert out.startswith("Exception encountered: Invalid house number")
37
+ assert exc_info.type == SystemExit
38
+ assert exc_info.value.code == 1
39
+
40
+
41
+ def test_get_data_check_uprn():
42
+ uprn = "1"
43
+ result = check_uprn(uprn)
44
+ assert result is True
45
+
46
+
47
+ def test_get_data_check_uprn_exception(capfd):
48
+ uprn = None
49
+ result = check_uprn(uprn)
50
+ out, err = capfd.readouterr()
51
+ assert out.startswith("Exception encountered: ")
52
+
53
+
54
+ def test_get_data_check_usrn():
55
+ usrn = "1"
56
+ result = check_usrn(usrn)
57
+ assert result is True
58
+
59
+
60
+ def test_get_data_check_usrn_exception(capfd):
61
+ usrn = None
62
+ result = check_usrn(usrn)
63
+ out, err = capfd.readouterr()
64
+ assert out.startswith("Exception encountered: ")
65
+
66
+
67
+ def test_get_date_with_ordinal():
68
+ date_number = 1
69
+ result = get_date_with_ordinal(date_number)
70
+ assert result == "1st"
71
+
72
+
73
+ def test_get_date_with_ordinal_exception():
74
+ date_number = "a"
75
+ with pytest.raises(TypeError) as exc_info:
76
+ result = get_date_with_ordinal(date_number)
77
+ assert exc_info.type == TypeError
78
+ assert (
79
+ exc_info.value.args[0] == "not all arguments converted during string formatting"
80
+ )
81
+
82
+
83
+ def test_parse_header():
84
+ input_header = "i:am|:a:test:header|value:test"
85
+ result = parse_header(input_header)
86
+ assert result == {"i": "am", ":a": "test:header", "value": "test"}
87
+ assert type(result) is dict
88
+
89
+
90
+ # Mock data for holidays
91
+ mock_holidays = {
92
+ datetime(2023, 1, 1): "New Year's Day",
93
+ datetime(2023, 12, 25): "Christmas Day",
94
+ datetime(2023, 12, 26): "Boxing Day",
95
+ }
96
+
97
+
98
+ @patch("holidays.country_holidays")
99
+ def test_is_holiday_when_true(mock_holidays_func):
100
+ # Setting up the mock to return specific holidays
101
+ mock_holidays_func.return_value = mock_holidays
102
+
103
+ # Christmas Day is a holiday
104
+ assert is_holiday(datetime(2023, 12, 25), Region.ENG) is True
105
+
106
+
107
+ @patch("holidays.country_holidays")
108
+ def test_is_holiday_when_false(mock_holidays_func):
109
+ # Setting up the mock to return specific holidays
110
+ mock_holidays_func.return_value = mock_holidays
111
+
112
+ # January 2nd is not a holiday
113
+ assert is_holiday(datetime(2023, 1, 2), Region.ENG) is False
114
+
115
+
116
+ def holiday_effect(country_code, subdiv=None):
117
+ if subdiv == "ENG":
118
+ return {
119
+ datetime(2023, 12, 25): "Christmas Day",
120
+ datetime(2023, 12, 26): "Boxing Day",
121
+ }
122
+ elif subdiv == "SCT":
123
+ return {datetime(2023, 11, 30): "St Andrew's Day"}
124
+ return {}
125
+
126
+
127
+ @patch("holidays.country_holidays", side_effect=holiday_effect)
128
+ def test_is_holiday_different_region(mock_holidays_func):
129
+ # St Andrew's Day in Scotland
130
+ assert is_holiday(datetime(2023, 11, 30), Region.SCT) is True
131
+
132
+ # St Andrew's Day is not observed in England
133
+ assert is_holiday(datetime(2023, 11, 30), Region.ENG) is False
134
+
135
+
136
+ def test_remove_alpha_characters():
137
+ test_string = "12345abc12345"
138
+ result = remove_alpha_characters(test_string)
139
+ assert result == "1234512345"
140
+
141
+
142
+ def test_remove_alpha_characters_bad():
143
+ test_string = "12345abc12345"
144
+ result = remove_alpha_characters(test_string)
145
+ assert result != "12345abc12345"
146
+
147
+
148
+ def test_get_dates_every_x_days():
149
+ now = datetime(2023, 2, 25, 7, 7, 17, 748661)
150
+ result = get_dates_every_x_days(now, 5, 7)
151
+ assert len(result) == 7
152
+ assert result[6] == "27/03/2023"
153
+
154
+
155
+ def test_get_dates_every_x_days_bad():
156
+ now = datetime(2023, 2, 25, 7, 7, 17, 748661)
157
+ result = get_dates_every_x_days(now, 5, 7)
158
+ assert len(result) != 8
159
+ assert result[6] != "27/03/2022"
160
+
161
+
162
+ def test_remove_ordinal_indicator_from_date_string():
163
+ test_string = "June 12th 2022"
164
+ result = remove_ordinal_indicator_from_date_string(test_string)
165
+ assert result == "June 12 2022"
166
+
167
+
168
+ def test_remove_ordinal_indicator_from_date_string_bad():
169
+ test_string = "June 12th 2022"
170
+ result = remove_ordinal_indicator_from_date_string(test_string)
171
+ assert result != "June 12th 2022"
172
+
173
+
174
+ def test_get_weekday_dates_in_period():
175
+ now = datetime(2023, 2, 25, 7, 7, 17, 748661)
176
+ result = get_weekday_dates_in_period(now, 5, 7)
177
+ assert len(result) == 7
178
+ assert result[6] == "08/04/2023"
179
+
180
+
181
+ def test_get_weekday_dates_in_period_bad():
182
+ now = datetime(2023, 2, 25, 7, 7, 17, 748661)
183
+ result = get_weekday_dates_in_period(now, 5, 7)
184
+ assert len(result) != 8
185
+ assert result[6] != "08/04/20232"
186
+
187
+
188
+ def test_get_next_occurrence_from_day_month_false():
189
+ result = get_next_occurrence_from_day_month(datetime(2023, 12, 1))
190
+ assert result == datetime(2023, 12, 1, 0, 0)
191
+
192
+
193
+ def test_get_next_occurrence_from_day_month_true():
194
+ result = get_next_occurrence_from_day_month(datetime(2023, 1, 1))
195
+ assert result == pd.Timestamp("2024-01-01 00:00:00")
196
+
197
+
198
+ @patch("uk_bin_collection.common.load_data", return_value={})
199
+ @patch("uk_bin_collection.common.save_data")
200
+ def test_update_input_json(mock_save_data, mock_load_data):
201
+ update_input_json(
202
+ "test_council",
203
+ "TEST_URL",
204
+ "path/to/input.json",
205
+ postcode="TEST_POSTCODE",
206
+ uprn="TEST_UPRN",
207
+ web_driver="TEST_WEBDRIVER",
208
+ skip_get_url=True,
209
+ )
210
+ # Check that save_data was called with expected data
211
+ expected_data = {
212
+ "test_council": {
213
+ "wiki_name": "test_council",
214
+ "url": "TEST_URL",
215
+ "postcode": "TEST_POSTCODE",
216
+ "uprn": "TEST_UPRN",
217
+ "web_driver": "TEST_WEBDRIVER",
218
+ "skip_get_url": True,
219
+ }
220
+ }
221
+ mock_save_data.assert_called_once_with("path/to/input.json", expected_data)
222
+
223
+
224
+ @patch("uk_bin_collection.common.load_data")
225
+ @patch("uk_bin_collection.common.save_data")
226
+ def test_update_input_json_ioerror(mock_save_data, mock_load_data):
227
+ mock_load_data.side_effect = IOError("Unable to access file")
228
+
229
+ with patch("builtins.print") as mock_print:
230
+ update_input_json("test_council", "TEST_URL", "path/to/input.json")
231
+ mock_print.assert_called_once_with(
232
+ "Error updating the JSON file: Unable to access file"
233
+ )
234
+
235
+
236
+ @patch("uk_bin_collection.common.load_data")
237
+ @patch("uk_bin_collection.common.save_data")
238
+ def test_update_input_json_jsondecodeerror(mock_save_data, mock_load_data):
239
+ mock_load_data.side_effect = json.JSONDecodeError("Expecting value", "doc", 0)
240
+
241
+ with patch("builtins.print") as mock_print:
242
+ update_input_json("test_council", "TEST_URL", "path/to/input.json")
243
+ mock_print.assert_called_once_with(
244
+ "Failed to decode JSON, check the integrity of the input file."
245
+ )
246
+
247
+
248
+ def test_load_data_existing_file():
249
+ # Create a mock file with JSON content
250
+ mock_file_data = json.dumps({"key": "value"})
251
+ # Set up the mock to return a readable stream
252
+ m = mock_open(read_data=mock_file_data)
253
+ with patch("builtins.open", m):
254
+ with patch("os.path.exists", return_value=True):
255
+ data = load_data("path/to/mock/file.json")
256
+ assert data == {
257
+ "key": "value"
258
+ }, f"Data was {data} instead of {{'key': 'value'}}"
259
+
260
+
261
+ def test_load_data_non_existing_file():
262
+ # Simulate file not existing
263
+ with patch("os.path.exists", return_value=False):
264
+ data = load_data("path/to/nonexistent/file.json")
265
+ assert data == {}
266
+
267
+
268
+ def test_load_data_invalid_json():
269
+ # Create a mock file with invalid JSON content
270
+ mock_file_data = '{"key": "value"'
271
+ with patch("builtins.open", mock_open(read_data=mock_file_data)), patch(
272
+ "json.load", side_effect=json.JSONDecodeError("Expecting ',' delimiter", "", 0)
273
+ ):
274
+ data = load_data("path/to/invalid.json")
275
+ assert data == {} # Modify based on your desired behavior
276
+
277
+
278
+ def test_save_data_to_file():
279
+ # Mock the open function and simulate writing
280
+ mock_file = mock_open()
281
+ with patch("builtins.open", mock_file):
282
+ data = {"key": "value"}
283
+ save_data("path/to/mock/file.json", data)
284
+ # Ensure the mock was called correctly to open the file for writing
285
+ mock_file.assert_called_once_with("path/to/mock/file.json", "w")
286
+
287
+ # Now check what was written to the file
288
+ written_data = "".join(
289
+ call.args[0] for call in mock_file().write.call_args_list
290
+ )
291
+ expected_data = json.dumps(data, sort_keys=True, indent=4)
292
+ assert (
293
+ written_data == expected_data
294
+ ), "Data written to file does not match expected JSON data"
295
+
296
+
297
+ def test_save_data_io_error():
298
+ # Simulate an IOError
299
+ with patch("builtins.open", mock_open()) as mocked_file:
300
+ mocked_file.side_effect = IOError("Failed to write to file")
301
+ with pytest.raises(IOError):
302
+ save_data("path/to/mock/file.json", {"key": "value"})
303
+
304
+
305
+ def test_contains_date_with_valid_dates():
306
+ assert contains_date("2023-05-10")
307
+ assert contains_date("10th of December, 2021")
308
+ assert contains_date("March 15, 2020")
309
+ assert contains_date("01/31/2020")
310
+
311
+
312
+ def test_contains_date_with_invalid_dates():
313
+ assert not contains_date("not a date")
314
+ assert not contains_date("12345")
315
+ assert not contains_date("May 35, 2020") # Invalid day
316
+ assert not contains_date("2020-02-30") # Invalid date
317
+
318
+
319
+ def test_contains_date_with_fuzzy_true():
320
+ assert contains_date("Today is 13th of April, 2024", fuzzy=True)
321
+ assert contains_date("They met on June 20th last year", fuzzy=True)
322
+
323
+
324
+ def test_contains_date_with_fuzzy_false():
325
+ assert not contains_date("Today is 13th of April, 2024", fuzzy=False)
326
+ assert not contains_date("They met on June 20th last year", fuzzy=False)
327
+
328
+
329
+ def test_contains_date_with_mixed_content():
330
+ assert contains_date("Event starts on 2023-05-10 at 10:00 AM", fuzzy=True)
331
+ assert not contains_date("Event starts on 2023-05-10 at 10:00 AM", fuzzy=False)
332
+
333
+
334
+ def test_create_webdriver_local():
335
+ result = create_webdriver(None, headless=True, user_agent="FireFox")
336
+ assert result.name in ["chrome","chrome-headless-shell"]
337
+
338
+
339
+ def test_create_webdriver_remote_failure():
340
+ # Test the scenario where the remote server is not available
341
+ with pytest.raises(MaxRetryError) as exc_info:
342
+ create_webdriver("http://invalid-url:4444", False)