|
| 1 | +import collections |
1 | 2 | import datetime |
2 | 3 | import re |
3 | | -from typing import Any, Dict, Optional, Union |
| 4 | +from typing import Any, Dict, List, Optional, Union |
4 | 5 | from unittest import mock |
5 | 6 | from unittest.mock import MagicMock, patch |
6 | 7 |
|
|
19 | 20 |
|
20 | 21 |
|
21 | 22 | @pytest.fixture |
22 | | -def mock_pystac_client(dummy_stac_item): |
| 23 | +def mock_pystac_client(): |
23 | 24 | mock_client = MagicMock(spec=pystac_client.Client) |
24 | 25 |
|
25 | 26 | mock_client.get_collections.return_value = [ |
26 | 27 | MagicMock(id="collection-1"), |
27 | 28 | MagicMock(id="collection-2"), |
28 | 29 | ] |
29 | 30 |
|
30 | | - mock_item_search = MagicMock(spec=pystac_client.ItemSearch) |
31 | | - mock_item_search.items.return_value = [dummy_stac_item] |
32 | | - mock_client.search.return_value = mock_item_search |
33 | | - |
34 | 31 | with patch("pystac_client.Client.open", return_value=mock_client): |
35 | 32 | yield mock_client |
36 | 33 |
|
@@ -97,22 +94,6 @@ def _pystac_item( |
97 | 94 | ) |
98 | 95 |
|
99 | 96 |
|
100 | | -@pytest.fixture |
101 | | -def dummy_stac_item() -> pystac.Item: |
102 | | - properties = { |
103 | | - "datetime": "2020-05-22T00:00:00Z", |
104 | | - "some_property": "value", |
105 | | - } |
106 | | - return pystac.Item( |
107 | | - id="test", geometry=None, bbox=None, properties=properties, datetime=datetime.datetime(2020, 5, 22) |
108 | | - ) |
109 | | - |
110 | | - |
111 | | -@pytest.fixture |
112 | | -def dummy_series_no_item_id() -> pd.Series: |
113 | | - return pd.Series({"datetime": "2020-05-22T00:00:00Z", "some_property": "value"}, name="test") |
114 | | - |
115 | | - |
116 | 97 | @pytest.fixture |
117 | 98 | def bulk_dataframe(): |
118 | 99 | return pd.DataFrame( |
@@ -227,8 +208,19 @@ def test_initialize_from_df_with_geometry(self, mock_persists, job_db_not_exists |
227 | 208 | assert job_db_not_exists.has_geometry == True |
228 | 209 | assert job_db_not_exists.geometry_column == "geometry" |
229 | 210 |
|
230 | | - def test_series_from(self, job_db_exists, dummy_series_no_item_id, dummy_stac_item): |
231 | | - pdt.assert_series_equal(job_db_exists.series_from(dummy_stac_item), dummy_series_no_item_id) |
| 211 | + def test_series_from(self, job_db_exists): |
| 212 | + item = pystac.Item( |
| 213 | + id="test", |
| 214 | + geometry=None, |
| 215 | + bbox=None, |
| 216 | + properties={ |
| 217 | + "datetime": "2020-05-22T00:00:00Z", |
| 218 | + "some_property": "value", |
| 219 | + }, |
| 220 | + datetime=datetime.datetime(2020, 5, 22), |
| 221 | + ) |
| 222 | + expected = pd.Series({"datetime": "2020-05-22T00:00:00Z", "some_property": "value"}, name="test") |
| 223 | + pdt.assert_series_equal(job_db_exists.series_from(item), expected) |
232 | 224 |
|
233 | 225 | @pytest.mark.parametrize( |
234 | 226 | ["series", "expected"], |
@@ -328,18 +320,43 @@ def test_get_by_status_with_filter(self, job_db_exists): |
328 | 320 | method="GET", collections=["collection-1"], filter="\"properties.status\"='not_started'", max_items=None |
329 | 321 | ) |
330 | 322 |
|
331 | | - def test_get_by_status_result(self, job_db_exists): |
332 | | - df = job_db_exists.get_by_status(["not_started"]) |
| 323 | + def test_get_by_status_result(self, requests_mock): |
| 324 | + stac_api_url = "http://stacapi.test" |
| 325 | + dummy_stac_api = DummyStacApi(root_url=stac_api_url, requests_mock=requests_mock) |
| 326 | + dummy_stac_api.predefine_collection("collection-123") |
| 327 | + dummy_stac_api.predefine_item( |
| 328 | + collection_id="collection-123", |
| 329 | + item=_pystac_item(id="item-123", properties={"status": "not_started"}), |
| 330 | + ) |
| 331 | + dummy_stac_api.predefine_item( |
| 332 | + collection_id="collection-123", |
| 333 | + item=_pystac_item(id="item-456", properties={"status": "running"}), |
| 334 | + ) |
| 335 | + dummy_stac_api.predefine_item( |
| 336 | + collection_id="collection-123", |
| 337 | + item=_pystac_item(id="item-789", properties={"status": "not_started"}), |
| 338 | + ) |
| 339 | + |
| 340 | + job_db = STACAPIJobDatabase(collection_id="collection-123", stac_root_url=stac_api_url) |
333 | 341 |
|
334 | 342 | pdt.assert_frame_equal( |
335 | | - df, |
| 343 | + job_db.get_by_status(["not_started"]), |
336 | 344 | pd.DataFrame( |
337 | 345 | { |
338 | | - "item_id": ["test"], |
339 | | - "datetime": ["2020-05-22T00:00:00Z"], |
340 | | - "some_property": ["value"], |
| 346 | + "item_id": ["item-123", "item-789"], |
| 347 | + "status": ["not_started", "not_started"], |
| 348 | + "datetime": ["2025-06-07T00:00:00Z", "2025-06-07T00:00:00Z"], |
| 349 | + }, |
| 350 | + ), |
| 351 | + ) |
| 352 | + pdt.assert_frame_equal( |
| 353 | + job_db.get_by_status(["running"]), |
| 354 | + pd.DataFrame( |
| 355 | + { |
| 356 | + "item_id": ["item-456"], |
| 357 | + "status": ["running"], |
| 358 | + "datetime": ["2025-06-07T00:00:00Z"], |
341 | 359 | }, |
342 | | - index=[0], |
343 | 360 | ), |
344 | 361 | ) |
345 | 362 |
|
@@ -443,16 +460,16 @@ def sleep_mock(): |
443 | 460 | class DummyStacApi: |
444 | 461 | """Minimal dummy implementation of a STAC API for testing purposes.""" |
445 | 462 |
|
446 | | - def __init__(self, root_url: str, requests_mock): |
| 463 | + def __init__(self, *, root_url: str = "http://stacapi.test", requests_mock): |
447 | 464 | self.root_url = root_url.rstrip("/") |
448 | 465 | self._requests_mock = requests_mock |
449 | 466 |
|
450 | 467 | requests_mock.get(f"{self.root_url}/", json=self._get_root()) |
451 | | - self.collections = [] |
| 468 | + self.collections: List[dict] = [] |
452 | 469 | requests_mock.get(f"{self.root_url}/collections", json=self._get_collections) |
453 | 470 | requests_mock.post(f"{self.root_url}/collections", json=self._post_collections) |
454 | 471 |
|
455 | | - self.items: Dict[str, Dict[str, Any]] = {} |
| 472 | + self.items: Dict[str, Dict[str, Any]] = collections.defaultdict(dict) |
456 | 473 | requests_mock.post( |
457 | 474 | re.compile(rf"{self.root_url}/collections/[^/]+/bulk_items"), json=self._post_collections_bulk_items |
458 | 475 | ) |
@@ -485,15 +502,33 @@ def _post_collections(self, request, context): |
485 | 502 | self.collections.append(post_data) |
486 | 503 | return {} |
487 | 504 |
|
| 505 | + def predefine_collection(self, collection_id: str): |
| 506 | + """Pre-define a collection with the given ID.""" |
| 507 | + assert collection_id not in [c["id"] for c in self.collections] |
| 508 | + self.collections.append( |
| 509 | + pystac.Collection( |
| 510 | + id=collection_id, |
| 511 | + description=collection_id, |
| 512 | + extent=pystac.Extent( |
| 513 | + spatial=pystac.SpatialExtent([-180, -90, 180, 90]), |
| 514 | + temporal=pystac.TemporalExtent([None, None]), |
| 515 | + ), |
| 516 | + title=collection_id, |
| 517 | + ).to_dict() |
| 518 | + ) |
| 519 | + |
| 520 | + def predefine_item(self, *, collection_id: str, item: pystac.Item): |
| 521 | + if collection_id not in [c["id"] for c in self.collections]: |
| 522 | + self.predefine_collection(collection_id) |
| 523 | + self.items[collection_id][item.id] = item.to_dict() |
| 524 | + |
488 | 525 | def _post_collections_bulk_items(self, request, context): |
489 | 526 | """Handler of `POST /collections/{collection_id}/bulk_items` requests.""" |
490 | 527 | # extract the collection_id from the URL |
491 | 528 | collection_id = re.search("/collections/([^/]+)/bulk_items", request.url).group(1) |
492 | 529 | post_data = request.json() |
493 | 530 | # TODO handle insert/upsert method? |
494 | 531 | for item_id, item in post_data["items"].items(): |
495 | | - if collection_id not in self.items: |
496 | | - self.items[collection_id] = {} |
497 | 532 | self.items[collection_id][item_id] = item |
498 | 533 | return {} |
499 | 534 |
|
|
0 commit comments