|
8 | 8 | from unittest.mock import MagicMock, patch |
9 | 9 |
|
10 | 10 | import numpy as np |
11 | | -from pystac import Catalog, Collection, Item |
| 11 | +from pystac import Catalog, Item |
12 | 12 | from xarray import DataArray, Dataset |
13 | 13 |
|
14 | 14 | from deep_code.constants import ( |
@@ -141,42 +141,115 @@ def test_build_variable_catalog(self, mock_add_themes, mock_add_gcmd): |
141 | 141 | # Self href ends with var1/catalog.json |
142 | 142 | self.assertTrue(catalog.self_href.endswith("/var1/catalog.json")) |
143 | 143 |
|
144 | | - @patch("pystac.Catalog.from_file") |
145 | | - def test_update_product_base_catalog(self, mock_from_file): |
146 | | - """Test linking product catalog.""" |
147 | | - mock_cat = MagicMock(spec=Catalog) |
148 | | - mock_from_file.return_value = mock_cat |
149 | | - |
150 | | - result = self.generator.update_product_base_catalog("path.json") |
151 | | - self.assertIs(result, mock_cat) |
152 | | - mock_cat.add_link.assert_called_once() |
153 | | - mock_cat.set_self_href.assert_called_once_with(PRODUCT_BASE_CATALOG_SELF_HREF) |
154 | | - |
155 | | - @patch("pystac.Catalog.from_file") |
156 | | - def test_update_variable_base_catalog(self, mock_from_file): |
157 | | - """Test linking variable base catalog.""" |
158 | | - mock_cat = MagicMock(spec=Catalog) |
159 | | - mock_from_file.return_value = mock_cat |
| 144 | + def test_update_product_base_catalog(self): |
| 145 | + """Child link is appended; existing links (including self) are untouched.""" |
| 146 | + base = { |
| 147 | + "type": "Catalog", |
| 148 | + "id": "products", |
| 149 | + "stac_version": "1.0.0", |
| 150 | + "description": "Products", |
| 151 | + "links": [ |
| 152 | + { |
| 153 | + "rel": "self", |
| 154 | + "href": PRODUCT_BASE_CATALOG_SELF_HREF, |
| 155 | + "type": "application/json", |
| 156 | + } |
| 157 | + ], |
| 158 | + } |
| 159 | + import tempfile, json as _json |
| 160 | + |
| 161 | + with tempfile.NamedTemporaryFile( |
| 162 | + mode="w", suffix=".json", delete=False |
| 163 | + ) as tmp: |
| 164 | + _json.dump(base, tmp) |
| 165 | + tmp_path = tmp.name |
| 166 | + |
| 167 | + result = self.generator.update_product_base_catalog(tmp_path) |
| 168 | + |
| 169 | + import os |
| 170 | + |
| 171 | + os.unlink(tmp_path) |
| 172 | + |
| 173 | + self.assertIsInstance(result, dict) |
| 174 | + rels = [lnk["rel"] for lnk in result["links"]] |
| 175 | + # self link must still be present and still first |
| 176 | + self.assertEqual(result["links"][0]["rel"], "self") |
| 177 | + self.assertEqual(result["links"][0]["href"], PRODUCT_BASE_CATALOG_SELF_HREF) |
| 178 | + self.assertIn("child", rels) |
| 179 | + child = next(lnk for lnk in result["links"] if lnk["rel"] == "child") |
| 180 | + self.assertIn("mock-collection-id", child["href"]) |
| 181 | + |
| 182 | + def test_update_variable_base_catalog(self): |
| 183 | + """Child links for each variable are appended.""" |
| 184 | + base = { |
| 185 | + "type": "Catalog", |
| 186 | + "id": "variables", |
| 187 | + "stac_version": "1.0.0", |
| 188 | + "description": "Variables", |
| 189 | + "links": [ |
| 190 | + { |
| 191 | + "rel": "self", |
| 192 | + "href": VARIABLE_BASE_CATALOG_SELF_HREF, |
| 193 | + "type": "application/json", |
| 194 | + } |
| 195 | + ], |
| 196 | + } |
| 197 | + import tempfile, json as _json, os |
| 198 | + |
| 199 | + with tempfile.NamedTemporaryFile( |
| 200 | + mode="w", suffix=".json", delete=False |
| 201 | + ) as tmp: |
| 202 | + _json.dump(base, tmp) |
| 203 | + tmp_path = tmp.name |
160 | 204 |
|
161 | 205 | vars_ = ["v1", "v2"] |
162 | | - result = self.generator.update_variable_base_catalog("vars.json", vars_) |
163 | | - self.assertIs(result, mock_cat) |
164 | | - # Expect one add_link per variable |
165 | | - self.assertEqual(mock_cat.add_link.call_count, len(vars_)) |
166 | | - mock_cat.set_self_href.assert_called_once_with(VARIABLE_BASE_CATALOG_SELF_HREF) |
167 | | - |
168 | | - @patch("pystac.Collection.from_file") |
169 | | - def test_update_deepesdl_collection(self, mock_from_file): |
170 | | - """Test updating DeepESDL collection.""" |
171 | | - mock_coll = MagicMock(spec=Collection) |
172 | | - mock_from_file.return_value = mock_coll |
173 | | - |
174 | | - result = self.generator.update_deepesdl_collection("deep.json") |
175 | | - self.assertIs(result, mock_coll) |
176 | | - # Expect child and theme related links for each theme |
177 | | - calls = mock_coll.add_link.call_count |
178 | | - self.assertGreaterEqual(calls, 1 + len(self.generator.osc_themes)) |
179 | | - mock_coll.set_self_href.assert_called_once_with(DEEPESDL_COLLECTION_SELF_HREF) |
| 206 | + result = self.generator.update_variable_base_catalog(tmp_path, vars_) |
| 207 | + os.unlink(tmp_path) |
| 208 | + |
| 209 | + self.assertIsInstance(result, dict) |
| 210 | + child_hrefs = [ |
| 211 | + lnk["href"] for lnk in result["links"] if lnk["rel"] == "child" |
| 212 | + ] |
| 213 | + self.assertEqual(len(child_hrefs), len(vars_)) |
| 214 | + # self link must remain in place |
| 215 | + self.assertEqual(result["links"][0]["rel"], "self") |
| 216 | + |
| 217 | + def test_update_deepesdl_collection(self): |
| 218 | + """Child and theme-related links are appended; existing links kept.""" |
| 219 | + base = { |
| 220 | + "type": "Collection", |
| 221 | + "id": "deep-esdl", |
| 222 | + "stac_version": "1.0.0", |
| 223 | + "description": "DeepESDL", |
| 224 | + "extent": {}, |
| 225 | + "links": [ |
| 226 | + { |
| 227 | + "rel": "self", |
| 228 | + "href": DEEPESDL_COLLECTION_SELF_HREF, |
| 229 | + "type": "application/json", |
| 230 | + } |
| 231 | + ], |
| 232 | + } |
| 233 | + import tempfile, json as _json, os |
| 234 | + |
| 235 | + with tempfile.NamedTemporaryFile( |
| 236 | + mode="w", suffix=".json", delete=False |
| 237 | + ) as tmp: |
| 238 | + _json.dump(base, tmp) |
| 239 | + tmp_path = tmp.name |
| 240 | + |
| 241 | + result = self.generator.update_deepesdl_collection(tmp_path) |
| 242 | + os.unlink(tmp_path) |
| 243 | + |
| 244 | + self.assertIsInstance(result, dict) |
| 245 | + rels = [lnk["rel"] for lnk in result["links"]] |
| 246 | + # child link added |
| 247 | + self.assertIn("child", rels) |
| 248 | + # one related link per theme |
| 249 | + related = [lnk for lnk in result["links"] if lnk["rel"] == "related"] |
| 250 | + self.assertGreaterEqual(len(related), len(self.generator.osc_themes)) |
| 251 | + # self link still present |
| 252 | + self.assertEqual(result["links"][0]["rel"], "self") |
180 | 253 |
|
181 | 254 | # ------------------------------------------------------------------ |
182 | 255 | # Zarr STAC Item / Catalog generation |
@@ -280,31 +353,39 @@ def test_build_zarr_stac_catalog_file_dict_content(self): |
280 | 353 | self.assertIn("zarr-data", item_dict["assets"]) |
281 | 354 | self.assertIn("zarr-consolidated-metadata", item_dict["assets"]) |
282 | 355 |
|
283 | | - def test_build_dataset_stac_collection_adds_s3_catalog_child_link(self): |
284 | | - """Child link to S3 catalog is added when stac_catalog_s3_root is provided.""" |
| 356 | + def test_build_dataset_stac_collection_adds_s3_catalog_via_link(self): |
| 357 | + """A 'via' link to the S3 catalog is added when stac_catalog_s3_root is provided. |
| 358 | +
|
| 359 | + rel='via' is used (not 'child') because the OSC validator requires every |
| 360 | + 'child' link to resolve to a file inside the metadata repository. |
| 361 | + """ |
285 | 362 | s3_root = "s3://test-bucket/stac/my-collection/" |
286 | 363 | collection = self.generator.build_dataset_stac_collection( |
287 | 364 | mode="dataset", stac_catalog_s3_root=s3_root |
288 | 365 | ) |
289 | | - child_links = [lnk for lnk in collection.links if lnk.rel == "child"] |
290 | | - s3_child = next( |
291 | | - (lnk for lnk in child_links if "s3://" in str(lnk.target)), None |
| 366 | + s3_via = next( |
| 367 | + ( |
| 368 | + lnk |
| 369 | + for lnk in collection.links |
| 370 | + if lnk.rel == "via" and "catalog.json" in str(lnk.target) |
| 371 | + ), |
| 372 | + None, |
292 | 373 | ) |
293 | | - self.assertIsNotNone(s3_child, "Expected a child link pointing to S3 catalog") |
| 374 | + self.assertIsNotNone(s3_via, "Expected a 'via' link pointing to S3 catalog") |
294 | 375 | self.assertEqual( |
295 | | - s3_child.target, |
| 376 | + s3_via.target, |
296 | 377 | "s3://test-bucket/stac/my-collection/catalog.json", |
297 | 378 | ) |
298 | 379 |
|
299 | | - def test_build_dataset_stac_collection_no_s3_child_link_by_default(self): |
300 | | - """No S3 child link is added when stac_catalog_s3_root is absent.""" |
| 380 | + def test_build_dataset_stac_collection_no_s3_via_link_by_default(self): |
| 381 | + """No S3 catalog 'via' link is added when stac_catalog_s3_root is absent.""" |
301 | 382 | collection = self.generator.build_dataset_stac_collection(mode="dataset") |
302 | | - s3_child_links = [ |
| 383 | + s3_catalog_links = [ |
303 | 384 | lnk |
304 | 385 | for lnk in collection.links |
305 | | - if lnk.rel == "child" and "s3://" in str(getattr(lnk, "target", "")) |
| 386 | + if lnk.rel == "via" and "catalog.json" in str(getattr(lnk, "target", "")) |
306 | 387 | ] |
307 | | - self.assertEqual(len(s3_child_links), 0) |
| 388 | + self.assertEqual(len(s3_catalog_links), 0) |
308 | 389 |
|
309 | 390 |
|
310 | 391 | class TestFormatString(unittest.TestCase): |
|
0 commit comments