Coverage for tests/test_places.py: 100%
78 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-07-22 06:43 -0400
« prev ^ index » next coverage.py v7.6.1, created at 2025-07-22 06:43 -0400
1"""Test the places.py module."""
3from copy import deepcopy
4from typing import Any
6import pydantic
7import pytest
9from src.overturetoosm.objects import ConfidenceError, UnmatchedError
10from src.overturetoosm.places import process_place
11from src.overturetoosm.utils import process_geojson
14@pytest.fixture(name="clean_dict")
15def clean_fix() -> dict[str, Any]:
16 """Fixture with the clean place properties."""
17 return {
18 "name": "Primary Name",
19 "brand": "Brand Name",
20 "brand:wikidata": "Q123",
21 "addr:street_address": "123 Main St",
22 "addr:city": "City",
23 "addr:postcode": "12345",
24 "addr:state": "CA",
25 "addr:country": "US",
26 "phone": "+1234567890",
27 "website": "https://example.com/",
28 "source": "dataset1 via overturetoosm",
29 "office": "lawyer",
30 "lawyer": "notary",
31 "contact:facebook": "https://www.facebook.com/example/",
32 }
35@pytest.fixture(name="geojson_dict")
36def geojson_fix() -> dict[str, Any]:
37 """Fixture with a mock place geojson."""
38 return {
39 "type": "FeatureCollection",
40 "features": [
41 {
42 "type": "Feature",
43 "geometry": {"type": "Point", "coordinates": [-1, 1]},
44 "properties": {
45 "id": "123",
46 "version": 1,
47 "update_time": "2022-01-01T00:00:00Z",
48 "sources": [
49 {
50 "property": "property1",
51 "dataset": "dataset1",
52 "record_id": "record1",
53 "confidence": 0.8,
54 }
55 ],
56 "names": {
57 "primary": "Primary Name",
58 "common": {"ex": "Example common name"},
59 "rules": [{"variant": "short", "value": "EX"}],
60 },
61 "brand": {
62 "wikidata": "Q123",
63 "names": {
64 "primary": "Brand Name",
65 "common": {"ex": "Example common name"},
66 "rules": [{"variant": "short", "value": "EX"}],
67 },
68 },
69 "categories": {
70 "main": "notary_public",
71 "alternate": ["alternate_category1", "alternate_category2"],
72 },
73 "confidence": 0.8,
74 "websites": ["https://example.com/"],
75 "socials": ["https://www.facebook.com/example/"],
76 "phones": ["+1234567890"],
77 "addresses": [
78 {
79 "freeform": "123 Main St",
80 "locality": "City",
81 "postcode": "12345",
82 "region": "CA",
83 "country": "US",
84 }
85 ],
86 },
87 }
88 ],
89 }
92@pytest.fixture(name="props_dict")
93def props_fix() -> dict[str, Any]:
94 """Fixture with the raw place properties."""
95 return {
96 "id": "123",
97 "version": 1,
98 "update_time": "2022-01-01T00:00:00Z",
99 "sources": [
100 {
101 "property": "property1",
102 "dataset": "dataset1",
103 "record_id": "record1",
104 "confidence": 0.8,
105 }
106 ],
107 "names": {
108 "primary": "Primary Name",
109 "common": {"ex": "Example common name"},
110 "rules": [{"variant": "short", "value": "EX"}],
111 },
112 "brand": {
113 "wikidata": "Q123",
114 "names": {
115 "primary": "Brand Name",
116 "common": {"ex": "Example common name"},
117 "rules": [{"variant": "short", "value": "EX"}],
118 },
119 },
120 "categories": {
121 "main": "notary_public",
122 "alternate": ["alternate_category1", "alternate_category2"],
123 },
124 "confidence": 0.8,
125 "websites": ["https://example.com/"],
126 "socials": ["https://www.facebook.com/example/"],
127 "phones": ["+1234567890"],
128 "addresses": [
129 {
130 "freeform": "123 Main St",
131 "locality": "City",
132 "postcode": "12345",
133 "region": "CA",
134 "country": "US",
135 }
136 ],
137 }
140def test_place_props(props_dict: dict, clean_dict: dict) -> None:
141 """Test that all properties are processed correctly."""
142 new_props = process_place(props_dict)
143 assert new_props == clean_dict
146def test_place_props_no_brand(props_dict: dict, clean_dict: dict) -> None:
147 """Test that all properties are processed correctly."""
148 props_dict.pop("brand", None)
149 new_props = process_place(props_dict)
150 for i in ["brand", "brand:wikidata"]:
151 clean_dict.pop(i, None)
152 assert new_props == clean_dict
155def test_place_props_no_category(props_dict: dict, clean_dict: dict) -> None:
156 """Test that all properties are processed correctly."""
157 props_dict.pop("categories", None)
158 new_props = process_place(props_dict)
159 for i in ["office", "lawyer"]:
160 clean_dict.pop(i, None)
161 assert new_props == clean_dict
164def test_place_props_twitter(props_dict: dict, clean_dict: dict) -> None:
165 """Test that all properties are processed correctly."""
166 props_dict["socials"].append("https://twitter.com/example/")
167 new_props = process_place(props_dict)
168 clean_dict["contact:twitter"] = "https://twitter.com/example/"
169 assert new_props == clean_dict
172def test_low_confidence(props_dict) -> None:
173 """Test that properties with low confidence are not processed."""
174 with pytest.raises(ConfidenceError):
175 process_place(props_dict, confidence=0.9)
178def test_confidence(props_dict) -> None:
179 """Test that invalid properties are not processed."""
180 props_dict["confidence"] = -0.1
181 with pytest.raises(pydantic.ValidationError):
182 process_place(props_dict)
185def test_unmatched_error(props_dict) -> None:
186 """Test that invalid properties are not processed."""
187 props_dict["categories"]["main"] = "invalid_category"
188 with pytest.raises(UnmatchedError):
189 process_place(props_dict, unmatched="error")
192def test_unmatched_ignore(props_dict, clean_dict: dict) -> None:
193 """Test that invalid properties are not processed."""
194 props_dict["categories"]["main"] = "invalid_category"
195 for i in ["office", "lawyer"]:
196 clean_dict.pop(i, None)
197 assert process_place(props_dict, unmatched="ignore") == clean_dict
200def test_unmatched_force(props_dict, clean_dict: dict) -> None:
201 """Test that invalid properties are not processed."""
202 cat = "invalid_category"
203 props_dict["categories"]["main"] = cat
204 for i in ["office", "lawyer"]:
205 clean_dict.pop(i, None)
206 clean_dict["type"] = cat
207 assert process_place(props_dict, unmatched="force") == clean_dict
210def test_place_geojson(geojson_dict, clean_dict: dict) -> None:
211 """Test that all properties are processed correctly."""
212 assert process_geojson(geojson=geojson_dict, fx=process_place) == {
213 "type": "FeatureCollection",
214 "features": [
215 {
216 "type": "Feature",
217 "geometry": {"type": "Point", "coordinates": [-1, 1]},
218 "properties": clean_dict,
219 }
220 ],
221 }
224def test_place_geojson_error(geojson_dict) -> None:
225 """Test that all properties are processed correctly when error flag is set."""
226 copy = deepcopy(geojson_dict)
227 copy["features"][0]["properties"]["categories"]["main"] = "invalid_category"
228 assert process_geojson(
229 geojson=copy, fx=process_place, options={"unmatched": "error"}
230 ) == {"type": "FeatureCollection", "features": []}
233def test_place_geojson_force(geojson_dict, clean_dict: dict) -> None:
234 """Test that all properties are processed correctly when force flag is set."""
235 copy = deepcopy(geojson_dict)
236 copy["features"][0]["properties"]["categories"]["main"] = "invalid_category"
237 clean_dict["type"] = "invalid_category"
238 for i in ["office", "lawyer"]:
239 clean_dict.pop(i, None)
240 assert process_geojson(
241 geojson=copy, fx=process_place, options={"unmatched": "force"}
242 ) == {
243 "type": "FeatureCollection",
244 "features": [
245 {
246 "type": "Feature",
247 "geometry": {"type": "Point", "coordinates": [-1, 1]},
248 "properties": clean_dict,
249 }
250 ],
251 }
254def test_place_geojson_ignore(geojson_dict, clean_dict: dict) -> None:
255 """Test that all properties are processed correctly when ignore flag is set."""
256 copy = deepcopy(geojson_dict)
257 copy["features"][0]["properties"]["categories"]["main"] = "invalid_category"
258 for i in ["office", "lawyer"]:
259 clean_dict.pop(i, None)
260 assert process_geojson(
261 geojson=copy, fx=process_place, options={"unmatched": "ignore"}
262 ) == {
263 "type": "FeatureCollection",
264 "features": [
265 {
266 "type": "Feature",
267 "geometry": {"type": "Point", "coordinates": [-1, 1]},
268 "properties": clean_dict,
269 }
270 ],
271 }