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.
- uk_bin_collection/README.rst +0 -0
- uk_bin_collection/tests/council_feature_input_parity.py +79 -0
- uk_bin_collection/tests/features/environment.py +7 -0
- uk_bin_collection/tests/features/validate_council_outputs.feature +767 -0
- uk_bin_collection/tests/input.json +1077 -0
- uk_bin_collection/tests/output.schema +41 -0
- uk_bin_collection/tests/step_defs/step_helpers/file_handler.py +46 -0
- uk_bin_collection/tests/step_defs/test_validate_council.py +87 -0
- uk_bin_collection/tests/test_collect_data.py +104 -0
- uk_bin_collection/tests/test_common_functions.py +342 -0
- uk_bin_collection/uk_bin_collection/collect_data.py +133 -0
- uk_bin_collection/uk_bin_collection/common.py +292 -0
- uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py +43 -0
- uk_bin_collection/uk_bin_collection/councils/ArunCouncil.py +97 -0
- uk_bin_collection/uk_bin_collection/councils/AylesburyValeCouncil.py +69 -0
- uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py +51 -0
- uk_bin_collection/uk_bin_collection/councils/BarnetCouncil.py +180 -0
- uk_bin_collection/uk_bin_collection/councils/BarnsleyMBCouncil.py +109 -0
- uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py +72 -0
- uk_bin_collection/uk_bin_collection/councils/BathAndNorthEastSomersetCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/BedfordBoroughCouncil.py +49 -0
- uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py +70 -0
- uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py +147 -0
- uk_bin_collection/uk_bin_collection/councils/BirminghamCityCouncil.py +119 -0
- uk_bin_collection/uk_bin_collection/councils/BlackburnCouncil.py +105 -0
- uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py +104 -0
- uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py +103 -0
- uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py +137 -0
- uk_bin_collection/uk_bin_collection/councils/BristolCityCouncil.py +141 -0
- uk_bin_collection/uk_bin_collection/councils/BromleyBoroughCouncil.py +115 -0
- uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py +107 -0
- uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py +95 -0
- uk_bin_collection/uk_bin_collection/councils/BuryCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/CalderdaleCouncil.py +123 -0
- uk_bin_collection/uk_bin_collection/councils/CannockChaseDistrictCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/CardiffCouncil.py +172 -0
- uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py +96 -0
- uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py +54 -0
- uk_bin_collection/uk_bin_collection/councils/ChelmsfordCityCouncil.py +127 -0
- uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py +32 -0
- uk_bin_collection/uk_bin_collection/councils/CheshireWestAndChesterCouncil.py +125 -0
- uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/ConwyCountyBorough.py +27 -0
- uk_bin_collection/uk_bin_collection/councils/CrawleyBoroughCouncil.py +61 -0
- uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py +291 -0
- uk_bin_collection/uk_bin_collection/councils/DerbyshireDalesDistrictCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/DoncasterCouncil.py +77 -0
- uk_bin_collection/uk_bin_collection/councils/DorsetCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py +41 -0
- uk_bin_collection/uk_bin_collection/councils/DurhamCouncil.py +49 -0
- uk_bin_collection/uk_bin_collection/councils/EastCambridgeshireCouncil.py +44 -0
- uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py +74 -0
- uk_bin_collection/uk_bin_collection/councils/EastLindseyDistrictCouncil.py +108 -0
- uk_bin_collection/uk_bin_collection/councils/EastRidingCouncil.py +142 -0
- uk_bin_collection/uk_bin_collection/councils/EastSuffolkCouncil.py +112 -0
- uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py +70 -0
- uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py +48 -0
- uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py +61 -0
- uk_bin_collection/uk_bin_collection/councils/FenlandDistrictCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/ForestOfDeanDistrictCouncil.py +113 -0
- uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py +118 -0
- uk_bin_collection/uk_bin_collection/councils/GedlingBoroughCouncil.py +1580 -0
- uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +55 -0
- uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py +150 -0
- uk_bin_collection/uk_bin_collection/councils/HaltonBoroughCouncil.py +142 -0
- uk_bin_collection/uk_bin_collection/councils/HaringeyCouncil.py +59 -0
- uk_bin_collection/uk_bin_collection/councils/HarrogateBoroughCouncil.py +63 -0
- uk_bin_collection/uk_bin_collection/councils/HighPeakCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/HullCityCouncil.py +48 -0
- uk_bin_collection/uk_bin_collection/councils/HuntingdonDistrictCouncil.py +44 -0
- uk_bin_collection/uk_bin_collection/councils/KingstonUponThamesCouncil.py +84 -0
- uk_bin_collection/uk_bin_collection/councils/KirkleesCouncil.py +130 -0
- uk_bin_collection/uk_bin_collection/councils/KnowsleyMBCouncil.py +139 -0
- uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py +71 -0
- uk_bin_collection/uk_bin_collection/councils/LeedsCityCouncil.py +137 -0
- uk_bin_collection/uk_bin_collection/councils/LisburnCastlereaghCityCouncil.py +101 -0
- uk_bin_collection/uk_bin_collection/councils/LiverpoolCityCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/LondonBoroughHounslow.py +82 -0
- uk_bin_collection/uk_bin_collection/councils/LondonBoroughRedbridge.py +161 -0
- uk_bin_collection/uk_bin_collection/councils/MaldonDistrictCouncil.py +52 -0
- uk_bin_collection/uk_bin_collection/councils/MalvernHillsDC.py +57 -0
- uk_bin_collection/uk_bin_collection/councils/ManchesterCityCouncil.py +106 -0
- uk_bin_collection/uk_bin_collection/councils/MansfieldDistrictCouncil.py +38 -0
- uk_bin_collection/uk_bin_collection/councils/MertonCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/MidAndEastAntrimBoroughCouncil.py +128 -0
- uk_bin_collection/uk_bin_collection/councils/MidSussexDistrictCouncil.py +80 -0
- uk_bin_collection/uk_bin_collection/councils/MiltonKeynesCityCouncil.py +54 -0
- uk_bin_collection/uk_bin_collection/councils/MoleValleyDistrictCouncil.py +98 -0
- uk_bin_collection/uk_bin_collection/councils/NeathPortTalbotCouncil.py +139 -0
- uk_bin_collection/uk_bin_collection/councils/NewarkAndSherwoodDC.py +52 -0
- uk_bin_collection/uk_bin_collection/councils/NewcastleCityCouncil.py +57 -0
- uk_bin_collection/uk_bin_collection/councils/NewhamCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py +203 -0
- uk_bin_collection/uk_bin_collection/councils/NorthEastDerbyshireDistrictCouncil.py +115 -0
- uk_bin_collection/uk_bin_collection/councils/NorthEastLincs.py +53 -0
- uk_bin_collection/uk_bin_collection/councils/NorthKestevenDistrictCouncil.py +45 -0
- uk_bin_collection/uk_bin_collection/councils/NorthLanarkshireCouncil.py +46 -0
- uk_bin_collection/uk_bin_collection/councils/NorthLincolnshireCouncil.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/NorthNorfolkDistrictCouncil.py +108 -0
- uk_bin_collection/uk_bin_collection/councils/NorthNorthamptonshireCouncil.py +72 -0
- uk_bin_collection/uk_bin_collection/councils/NorthSomersetCouncil.py +76 -0
- uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py +220 -0
- uk_bin_collection/uk_bin_collection/councils/NorthWestLeicestershire.py +114 -0
- uk_bin_collection/uk_bin_collection/councils/NorthYorkshire.py +58 -0
- uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +123 -0
- uk_bin_collection/uk_bin_collection/councils/NottinghamCityCouncil.py +36 -0
- uk_bin_collection/uk_bin_collection/councils/OldhamCouncil.py +51 -0
- uk_bin_collection/uk_bin_collection/councils/PortsmouthCityCouncil.py +131 -0
- uk_bin_collection/uk_bin_collection/councils/PrestonCityCouncil.py +97 -0
- uk_bin_collection/uk_bin_collection/councils/ReadingBoroughCouncil.py +30 -0
- uk_bin_collection/uk_bin_collection/councils/ReigateAndBansteadBoroughCouncil.py +81 -0
- uk_bin_collection/uk_bin_collection/councils/RenfrewshireCouncil.py +135 -0
- uk_bin_collection/uk_bin_collection/councils/RhonddaCynonTaffCouncil.py +80 -0
- uk_bin_collection/uk_bin_collection/councils/RochdaleCouncil.py +69 -0
- uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py +60 -0
- uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py +93 -0
- uk_bin_collection/uk_bin_collection/councils/RushcliffeBoroughCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py +81 -0
- uk_bin_collection/uk_bin_collection/councils/SalfordCityCouncil.py +70 -0
- uk_bin_collection/uk_bin_collection/councils/SevenoaksDistrictCouncil.py +106 -0
- uk_bin_collection/uk_bin_collection/councils/SheffieldCityCouncil.py +54 -0
- uk_bin_collection/uk_bin_collection/councils/ShropshireCouncil.py +45 -0
- uk_bin_collection/uk_bin_collection/councils/SolihullCouncil.py +48 -0
- uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py +203 -0
- uk_bin_collection/uk_bin_collection/councils/SouthAyrshireCouncil.py +73 -0
- uk_bin_collection/uk_bin_collection/councils/SouthCambridgeshireCouncil.py +65 -0
- uk_bin_collection/uk_bin_collection/councils/SouthGloucestershireCouncil.py +74 -0
- uk_bin_collection/uk_bin_collection/councils/SouthLanarkshireCouncil.py +78 -0
- uk_bin_collection/uk_bin_collection/councils/SouthNorfolkCouncil.py +91 -0
- uk_bin_collection/uk_bin_collection/councils/SouthOxfordshireCouncil.py +93 -0
- uk_bin_collection/uk_bin_collection/councils/SouthTynesideCouncil.py +98 -0
- uk_bin_collection/uk_bin_collection/councils/StAlbansCityAndDistrictCouncil.py +43 -0
- uk_bin_collection/uk_bin_collection/councils/StHelensBC.py +56 -0
- uk_bin_collection/uk_bin_collection/councils/StaffordshireMoorlandsDistrictCouncil.py +112 -0
- uk_bin_collection/uk_bin_collection/councils/StockportBoroughCouncil.py +39 -0
- uk_bin_collection/uk_bin_collection/councils/StokeOnTrentCityCouncil.py +79 -0
- uk_bin_collection/uk_bin_collection/councils/StratfordUponAvonCouncil.py +94 -0
- uk_bin_collection/uk_bin_collection/councils/SunderlandCityCouncil.py +100 -0
- uk_bin_collection/uk_bin_collection/councils/SwaleBoroughCouncil.py +52 -0
- uk_bin_collection/uk_bin_collection/councils/TamesideMBCouncil.py +62 -0
- uk_bin_collection/uk_bin_collection/councils/TandridgeDistrictCouncil.py +60 -0
- uk_bin_collection/uk_bin_collection/councils/TelfordAndWrekinCouncil.py +50 -0
- uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py +203 -0
- uk_bin_collection/uk_bin_collection/councils/TonbridgeAndMallingBC.py +101 -0
- uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py +51 -0
- uk_bin_collection/uk_bin_collection/councils/TorridgeDistrictCouncil.py +154 -0
- uk_bin_collection/uk_bin_collection/councils/ValeofGlamorganCouncil.py +119 -0
- uk_bin_collection/uk_bin_collection/councils/ValeofWhiteHorseCouncil.py +103 -0
- uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py +89 -0
- uk_bin_collection/uk_bin_collection/councils/WarwickDistrictCouncil.py +34 -0
- uk_bin_collection/uk_bin_collection/councils/WaverleyBoroughCouncil.py +119 -0
- uk_bin_collection/uk_bin_collection/councils/WealdenDistrictCouncil.py +86 -0
- uk_bin_collection/uk_bin_collection/councils/WelhatCouncil.py +73 -0
- uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/WestLindseyDistrictCouncil.py +118 -0
- uk_bin_collection/uk_bin_collection/councils/WestLothianCouncil.py +103 -0
- uk_bin_collection/uk_bin_collection/councils/WestNorthamptonshireCouncil.py +34 -0
- uk_bin_collection/uk_bin_collection/councils/WestSuffolkCouncil.py +64 -0
- uk_bin_collection/uk_bin_collection/councils/WiganBoroughCouncil.py +97 -0
- uk_bin_collection/uk_bin_collection/councils/WiltshireCouncil.py +135 -0
- uk_bin_collection/uk_bin_collection/councils/WindsorAndMaidenheadCouncil.py +134 -0
- uk_bin_collection/uk_bin_collection/councils/WokingBoroughCouncil.py +114 -0
- uk_bin_collection/uk_bin_collection/councils/WyreCouncil.py +89 -0
- uk_bin_collection/uk_bin_collection/councils/YorkCouncil.py +45 -0
- uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py +33 -0
- uk_bin_collection/uk_bin_collection/get_bin_data.py +165 -0
- uk_bin_collection-0.74.0.dist-info/LICENSE +21 -0
- uk_bin_collection-0.74.0.dist-info/METADATA +247 -0
- uk_bin_collection-0.74.0.dist-info/RECORD +171 -0
- uk_bin_collection-0.74.0.dist-info/WHEEL +4 -0
- 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)
|