Skip to content

Commit 69cd5de

Browse files
authored
Merge pull request #26 from chrislyonsKY/test/expand-integration-tests
Expand integration tests: 25+ tests across all 9 products
2 parents 926194c + 53925f8 commit 69cd5de

1 file changed

Lines changed: 206 additions & 0 deletions

File tree

tests/test_integration.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,24 @@
66
Run with: pytest tests/ -m integration -v
77
"""
88

9+
from __future__ import annotations
10+
11+
import tempfile
12+
from pathlib import Path
13+
914
import pytest
1015

1116
import abovepy
17+
from abovepy._exceptions import CountyError, ProductError
1218

1319
pytestmark = pytest.mark.integration
1420

1521

22+
# ---------------------------------------------------------------------------
23+
# STAC Connection & Search
24+
# ---------------------------------------------------------------------------
25+
26+
1627
class TestLiveSTACConnection:
1728
def test_client_connects(self):
1829
"""Client can connect and list collections."""
@@ -56,6 +67,109 @@ def test_info_all_products(self):
5667
"""info() returns metadata for all 9 products."""
5768
df = abovepy.info()
5869
assert len(df) == 9
70+
assert "product" in df.columns
71+
assert "resolution" in df.columns
72+
assert "format" in df.columns
73+
74+
75+
# ---------------------------------------------------------------------------
76+
# Product Coverage — search each of the 9 products
77+
# ---------------------------------------------------------------------------
78+
79+
80+
class TestProductCoverage:
81+
@pytest.mark.parametrize("product", [
82+
"dem_phase1", "dem_phase2", "dem_phase3",
83+
])
84+
def test_dem_products(self, frankfort_bbox, product):
85+
"""DEM products return tiles for Frankfort area."""
86+
tiles = abovepy.search(bbox=frankfort_bbox, product=product, max_items=3)
87+
assert len(tiles) > 0
88+
assert tiles.iloc[0]["product"] == product
89+
90+
@pytest.mark.parametrize("product", [
91+
"ortho_phase1", "ortho_phase2", "ortho_phase3",
92+
])
93+
def test_ortho_products(self, frankfort_bbox, product):
94+
"""Ortho products return tiles for Frankfort area."""
95+
tiles = abovepy.search(bbox=frankfort_bbox, product=product, max_items=3)
96+
assert len(tiles) > 0
97+
98+
@pytest.mark.parametrize("product", [
99+
"laz_phase1", "laz_phase2", "laz_phase3",
100+
])
101+
def test_laz_products(self, frankfort_bbox, product):
102+
"""LiDAR products return tiles for Frankfort area."""
103+
tiles = abovepy.search(bbox=frankfort_bbox, product=product, max_items=3)
104+
assert len(tiles) > 0
105+
106+
107+
# ---------------------------------------------------------------------------
108+
# County Search
109+
# ---------------------------------------------------------------------------
110+
111+
112+
class TestCountySearch:
113+
@pytest.mark.parametrize("county", [
114+
"Franklin", "Fayette", "Pike", "Jefferson",
115+
])
116+
def test_search_counties(self, county):
117+
"""Multiple counties return DEM results."""
118+
tiles = abovepy.search(county=county, product="dem_phase3", max_items=5)
119+
assert len(tiles) > 0
120+
121+
def test_county_case_insensitive(self):
122+
"""County search is case-insensitive."""
123+
tiles = abovepy.search(county="franklin", product="dem_phase3", max_items=3)
124+
assert len(tiles) > 0
125+
126+
def test_invalid_county_raises(self):
127+
"""Unknown county raises CountyError."""
128+
with pytest.raises(CountyError):
129+
abovepy.search(county="Atlantis", product="dem_phase3")
130+
131+
132+
# ---------------------------------------------------------------------------
133+
# Bbox Edge Cases
134+
# ---------------------------------------------------------------------------
135+
136+
137+
class TestBboxEdgeCases:
138+
def test_very_small_bbox(self):
139+
"""Very small bbox (single point area) still returns tiles."""
140+
tiny = (-84.85, 38.20, -84.849, 38.201)
141+
tiles = abovepy.search(bbox=tiny, product="dem_phase3", max_items=5)
142+
assert len(tiles) > 0
143+
144+
def test_bbox_on_ky_border(self):
145+
"""Bbox on KY southern border returns results."""
146+
border = (-84.5, 36.50, -84.3, 36.60)
147+
tiles = abovepy.search(bbox=border, product="dem_phase3", max_items=5)
148+
# May or may not have tiles right at the border, but shouldn't error
149+
assert isinstance(tiles, type(tiles))
150+
151+
def test_bbox_straddling_multiple_tiles(self):
152+
"""Large bbox should return many tiles."""
153+
large = (-85.0, 38.0, -84.5, 38.5)
154+
tiles = abovepy.search(bbox=large, product="dem_phase3", max_items=100)
155+
assert len(tiles) > 5 # Should span multiple tile grid cells
156+
157+
158+
# ---------------------------------------------------------------------------
159+
# Error Cases
160+
# ---------------------------------------------------------------------------
161+
162+
163+
class TestErrorCases:
164+
def test_invalid_product_raises(self):
165+
"""Invalid product key raises ProductError."""
166+
with pytest.raises(ProductError):
167+
abovepy.search(bbox=(-84.9, 38.15, -84.8, 38.25), product="invalid_product")
168+
169+
170+
# ---------------------------------------------------------------------------
171+
# Read
172+
# ---------------------------------------------------------------------------
59173

60174

61175
class TestLiveRead:
@@ -76,3 +190,95 @@ def test_read_full_tile(self, frankfort_bbox):
76190
data, profile = abovepy.read(url)
77191
assert data.shape[1] > 0
78192
assert data.shape[2] > 0
193+
194+
@pytest.mark.slow
195+
def test_read_returns_epsg3089(self, frankfort_bbox):
196+
"""Read tile CRS should be EPSG:3089."""
197+
tiles = abovepy.search(bbox=frankfort_bbox, product="dem_phase3", max_items=1)
198+
url = tiles.iloc[0]["asset_url"]
199+
_, profile = abovepy.read(url)
200+
crs_str = str(profile["crs"])
201+
assert "3089" in crs_str
202+
203+
204+
# ---------------------------------------------------------------------------
205+
# Download & Mosaic
206+
# ---------------------------------------------------------------------------
207+
208+
209+
class TestLiveDownloadAndMosaic:
210+
@pytest.mark.slow
211+
def test_download_single_tile(self, frankfort_bbox):
212+
"""Download a single DEM tile and verify it exists."""
213+
tiles = abovepy.search(bbox=frankfort_bbox, product="dem_phase3", max_items=1)
214+
with tempfile.TemporaryDirectory() as tmpdir:
215+
paths = abovepy.download(tiles, output_dir=tmpdir)
216+
assert len(paths) == 1
217+
assert paths[0].exists()
218+
assert paths[0].stat().st_size > 0
219+
assert paths[0].suffix == ".tif"
220+
221+
@pytest.mark.slow
222+
def test_download_skip_existing(self, frankfort_bbox):
223+
"""Second download should skip already-downloaded files."""
224+
tiles = abovepy.search(bbox=frankfort_bbox, product="dem_phase3", max_items=1)
225+
with tempfile.TemporaryDirectory() as tmpdir:
226+
paths1 = abovepy.download(tiles, output_dir=tmpdir)
227+
mtime1 = paths1[0].stat().st_mtime
228+
paths2 = abovepy.download(tiles, output_dir=tmpdir)
229+
mtime2 = paths2[0].stat().st_mtime
230+
assert mtime1 == mtime2 # File was not re-downloaded
231+
232+
@pytest.mark.slow
233+
def test_mosaic_vrt(self, frankfort_bbox):
234+
"""Download 2 tiles, mosaic to VRT, verify it's readable."""
235+
tiles = abovepy.search(bbox=frankfort_bbox, product="dem_phase3", max_items=2)
236+
if len(tiles) < 2:
237+
pytest.skip("Need at least 2 tiles for mosaic test")
238+
with tempfile.TemporaryDirectory() as tmpdir:
239+
paths = abovepy.download(tiles, output_dir=tmpdir)
240+
vrt_path = Path(tmpdir) / "mosaic.vrt"
241+
result = abovepy.mosaic(paths, output=vrt_path)
242+
assert Path(result).exists()
243+
assert str(result).endswith(".vrt")
244+
245+
246+
# ---------------------------------------------------------------------------
247+
# Info
248+
# ---------------------------------------------------------------------------
249+
250+
251+
class TestLiveInfo:
252+
def test_info_columns(self):
253+
"""info() DataFrame has expected columns."""
254+
df = abovepy.info()
255+
expected = {"product", "display_name", "format", "resolution", "phase"}
256+
assert expected.issubset(set(df.columns))
257+
258+
def test_info_products_complete(self):
259+
"""info() includes all 9 known products."""
260+
df = abovepy.info()
261+
products = set(df["product"])
262+
for p in ["dem_phase1", "dem_phase2", "dem_phase3",
263+
"ortho_phase1", "ortho_phase2", "ortho_phase3",
264+
"laz_phase1", "laz_phase2", "laz_phase3"]:
265+
assert p in products, f"Missing product: {p}"
266+
267+
268+
# ---------------------------------------------------------------------------
269+
# LiDAR (optional — only runs if laspy is installed)
270+
# ---------------------------------------------------------------------------
271+
272+
273+
class TestLiDAROptional:
274+
@pytest.mark.slow
275+
def test_laz_tile_url_accessible(self, frankfort_bbox):
276+
"""LAZ tile URLs from search should be HTTP accessible."""
277+
import httpx
278+
279+
tiles = abovepy.search(bbox=frankfort_bbox, product="laz_phase2", max_items=1)
280+
if tiles.empty:
281+
pytest.skip("No COPC tiles found in Frankfort area")
282+
url = tiles.iloc[0]["asset_url"]
283+
resp = httpx.head(url, follow_redirects=True, timeout=30)
284+
assert resp.status_code == 200

0 commit comments

Comments
 (0)