Skip to content

Commit cfdcfc1

Browse files
committed
ensure parent ids are removed
1 parent 11e7a4e commit cfdcfc1

File tree

2 files changed

+146
-42
lines changed

2 files changed

+146
-42
lines changed

stac_fastapi/core/stac_fastapi/core/extensions/catalogs.py

Lines changed: 34 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -360,52 +360,44 @@ async def delete_catalog(
360360
f"Failed to delete collection {coll_id}: {e}"
361361
)
362362
else:
363-
# Remove catalog link from each collection (orphan them)
363+
# Remove catalog from each collection's parent_ids and links (orphan them)
364364
for coll_id in collection_ids:
365365
try:
366-
collection = await self.client.get_collection(
367-
coll_id, request=request
366+
# Get the collection from database to access parent_ids
367+
collection_db = await self.client.database.find_collection(
368+
coll_id
368369
)
369-
# Remove the catalog link from the collection
370-
if hasattr(collection, "links"):
371-
collection.links = [
372-
link
373-
for link in collection.links
374-
if not (
375-
getattr(link, "rel", None) == "catalog"
376-
and catalog_id in getattr(link, "href", "")
377-
)
378-
]
379-
elif isinstance(collection, dict):
380-
collection["links"] = [
381-
link
382-
for link in collection.get("links", [])
383-
if not (
384-
link.get("rel") == "catalog"
385-
and catalog_id in link.get("href", "")
386-
)
387-
]
388370

389-
# Update the collection in the database
390-
collection_dict = (
391-
collection.model_dump(mode="json")
392-
if hasattr(collection, "model_dump")
393-
else collection
394-
)
395-
collection_db = (
396-
self.client.database.collection_serializer.stac_to_db(
397-
collection_dict, request
371+
# Remove catalog_id from parent_ids
372+
parent_ids = collection_db.get("parent_ids", [])
373+
if catalog_id in parent_ids:
374+
parent_ids.remove(catalog_id)
375+
collection_db["parent_ids"] = parent_ids
376+
377+
# Also remove the catalog link from the collection's links
378+
if "links" in collection_db:
379+
collection_db["links"] = [
380+
link
381+
for link in collection_db.get("links", [])
382+
if not (
383+
link.get("rel") == "catalog"
384+
and catalog_id in link.get("href", "")
385+
)
386+
]
387+
388+
# Update the collection in the database
389+
await self.client.database.update_collection(
390+
collection_id=coll_id,
391+
collection=collection_db,
392+
refresh=True,
393+
)
394+
logger.info(
395+
f"Removed catalog {catalog_id} from collection {coll_id} parent_ids and links"
396+
)
397+
else:
398+
logger.debug(
399+
f"Catalog {catalog_id} not in parent_ids for collection {coll_id}"
398400
)
399-
)
400-
await self.client.database.client.index(
401-
index=COLLECTIONS_INDEX,
402-
id=coll_id,
403-
body=collection_db.model_dump()
404-
if hasattr(collection_db, "model_dump")
405-
else collection_db,
406-
refresh=True,
407-
)
408-
logger.info(f"Removed catalog link from collection {coll_id}")
409401
except Exception as e:
410402
error_msg = str(e)
411403
if "not found" in error_msg.lower():
@@ -414,7 +406,7 @@ async def delete_catalog(
414406
)
415407
else:
416408
logger.warning(
417-
f"Failed to remove catalog link from collection {coll_id}: {e}"
409+
f"Failed to remove catalog {catalog_id} from collection {coll_id}: {e}"
418410
)
419411

420412
# Delete the catalog

stac_fastapi/tests/extensions/test_catalogs.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,64 @@ async def test_delete_catalog_no_cascade(catalogs_app_client, load_test_data):
605605
), "Collection should not have catalog link after catalog deletion"
606606

607607

608+
@pytest.mark.asyncio
609+
async def test_delete_catalog_removes_parent_ids_from_collections(
610+
catalogs_app_client, load_test_data
611+
):
612+
"""Test that deleting a catalog removes its ID from child collections' parent_ids."""
613+
# Create a catalog
614+
test_catalog = load_test_data("test_catalog.json")
615+
catalog_id = f"test-catalog-{uuid.uuid4()}"
616+
test_catalog["id"] = catalog_id
617+
test_catalog["links"] = [
618+
link for link in test_catalog.get("links", []) if link.get("rel") != "child"
619+
]
620+
621+
catalog_resp = await catalogs_app_client.post("/catalogs", json=test_catalog)
622+
assert catalog_resp.status_code == 201
623+
624+
# Create 3 collections in the catalog
625+
collection_ids = []
626+
for i in range(3):
627+
test_collection = load_test_data("test_collection.json")
628+
collection_id = f"test-collection-{uuid.uuid4()}-{i}"
629+
test_collection["id"] = collection_id
630+
631+
create_resp = await catalogs_app_client.post(
632+
f"/catalogs/{catalog_id}/collections", json=test_collection
633+
)
634+
assert create_resp.status_code == 201
635+
collection_ids.append(collection_id)
636+
637+
# Verify all collections have the catalog in their parent_ids
638+
# (indirectly verified by checking they're accessible via the catalog endpoint)
639+
get_collections_resp = await catalogs_app_client.get(
640+
f"/catalogs/{catalog_id}/collections"
641+
)
642+
assert get_collections_resp.status_code == 200
643+
collections_response = get_collections_resp.json()
644+
returned_ids = [col["id"] for col in collections_response["collections"]]
645+
for collection_id in collection_ids:
646+
assert collection_id in returned_ids
647+
648+
# Delete the catalog without cascade
649+
delete_resp = await catalogs_app_client.delete(f"/catalogs/{catalog_id}")
650+
assert delete_resp.status_code == 204
651+
652+
# Verify all collections still exist
653+
for collection_id in collection_ids:
654+
get_resp = await catalogs_app_client.get(f"/collections/{collection_id}")
655+
assert get_resp.status_code == 200
656+
657+
# Verify collections are no longer accessible via the deleted catalog
658+
# (This indirectly verifies parent_ids was updated)
659+
for collection_id in collection_ids:
660+
get_from_catalog_resp = await catalogs_app_client.get(
661+
f"/catalogs/{catalog_id}/collections/{collection_id}"
662+
)
663+
assert get_from_catalog_resp.status_code == 404
664+
665+
608666
@pytest.mark.asyncio
609667
async def test_create_catalog_collection_adds_parent_id(
610668
catalogs_app_client, load_test_data
@@ -880,6 +938,60 @@ async def test_delete_collection_not_in_catalog_returns_404(
880938
assert delete_resp.status_code == 404
881939

882940

941+
@pytest.mark.asyncio
942+
async def test_catalog_links_contain_all_collections(
943+
catalogs_app_client, load_test_data
944+
):
945+
"""Test that a catalog's links contain all 3 collections added to it."""
946+
# Create a catalog
947+
test_catalog = load_test_data("test_catalog.json")
948+
catalog_id = f"test-catalog-{uuid.uuid4()}"
949+
test_catalog["id"] = catalog_id
950+
# Remove any placeholder child links
951+
test_catalog["links"] = [
952+
link for link in test_catalog.get("links", []) if link.get("rel") != "child"
953+
]
954+
955+
catalog_resp = await catalogs_app_client.post("/catalogs", json=test_catalog)
956+
assert catalog_resp.status_code == 201
957+
958+
# Create 3 collections in the catalog
959+
collection_ids = []
960+
for i in range(3):
961+
test_collection = load_test_data("test_collection.json")
962+
collection_id = f"test-collection-{uuid.uuid4()}-{i}"
963+
test_collection["id"] = collection_id
964+
965+
create_resp = await catalogs_app_client.post(
966+
f"/catalogs/{catalog_id}/collections", json=test_collection
967+
)
968+
assert create_resp.status_code == 201
969+
collection_ids.append(collection_id)
970+
971+
# Get the catalog and verify all 3 collections are in its links
972+
catalog_get_resp = await catalogs_app_client.get(f"/catalogs/{catalog_id}")
973+
assert catalog_get_resp.status_code == 200
974+
975+
catalog_data = catalog_get_resp.json()
976+
catalog_links = catalog_data.get("links", [])
977+
978+
# Extract all child links (collection links)
979+
child_links = [link for link in catalog_links if link.get("rel") == "child"]
980+
981+
# Verify we have exactly 3 child links
982+
assert (
983+
len(child_links) == 3
984+
), f"Catalog should have 3 child links, but has {len(child_links)}"
985+
986+
# Verify each collection ID is in the child links
987+
child_hrefs = [link.get("href", "") for link in child_links]
988+
for collection_id in collection_ids:
989+
collection_href = f"/collections/{collection_id}"
990+
assert any(
991+
collection_href in href for href in child_hrefs
992+
), f"Collection {collection_id} missing from catalog links. Found links: {child_hrefs}"
993+
994+
883995
@pytest.mark.asyncio
884996
async def test_parent_ids_not_exposed_to_client(catalogs_app_client, load_test_data):
885997
"""Test that parent_ids field is not exposed in API responses."""

0 commit comments

Comments
 (0)