Old
", + "version": "v1", + }, + } + + put_payload = {} + + def _fake_get(url, params=None, **kwargs): + class R: + status_code = 200 + text = "ok" + def json(self): + # whatever object your test expects (e.g., deepcopy(existing)) + return deepcopy(existing) + def raise_for_status(self): + return None + return R() + + def _fake_post(url, params=None, json=None, **kwargs): + class R: + status_code = 200 + text = "ok" + def json(self): + # return what your code reads from POST responses, if anything + return {"links": {"bucket": "https://example-bucket"}} + def raise_for_status(self): + return None + return R() + + def _fake_put(url, params=None, data=None, headers=None, **kwargs): + class R: + status_code = 200 + text = "ok" + def raise_for_status(self): + return None + return R() + + uploaded = {} + + # zenodo-client upload shim: capture files that would be uploaded + def _fake_update_zenodo(deposition_id, paths, sandbox=True, access_token=None, publish=False): + self.assertEqual(deposition_id, "123456") + self.assertTrue(sandbox) + self.assertEqual(access_token, "tok") + names = {Path(p).name for p in paths} + self.assertIn("README.md", names) + self.assertIn("optimap-main.zip", names) + self.assertTrue(any(n.endswith(".geojson") for n in names)) + self.assertTrue(any(n.endswith(".gpkg") for n in names)) + uploaded["paths"] = [str(p) for p in paths] + class R: + def json(self): return {"links": {"html": f"https://sandbox.zenodo.org/deposit/{deposition_id}"}} + return R() + + with patch.object(self.deposit_mod, "__file__", new=self.deposit_file), \ + patch.object(self.deposit_mod, "Path", self.FakePath), \ + patch.object(self.deposit_mod.requests, "get", _fake_get), \ + patch.object(self.deposit_mod.requests, "put", _fake_put), \ + patch.object(self.deposit_mod, "update_zenodo", _fake_update_zenodo), \ + patch.object(self.deposit_mod, "_markdown_to_html", lambda s: "HTML
"), \ + override_settings(ZENODO_UPLOADS_ENABLED=True): + + call_command( + "deposit_zenodo", + "--deposition-id", "123456", + ) + + # Merged metadata: required fields preserved, description/version updated, related merged + merged = put_payload["metadata"] + self.assertEqual(merged["title"], "Existing Title") + self.assertEqual(merged["upload_type"], "dataset") + self.assertEqual(merged["publication_date"], "2025-07-14") + self.assertEqual(merged["creators"], [{"name": "OPTIMAP"}]) + + self.assertIn("description", merged) + self.assertTrue(merged["description"].startswith("HTML + + self.assertIsInstance(merged.get("version"), str) + rel = {(d["identifier"], d["relation"]) for d in merged.get("related_identifiers", [])} + self.assertIn(("https://old.example", "isSupplementTo"), rel) + self.assertIn(("https://optimap.science", "describes"), rel) + + # Uploader called with expected files + self.assertIn("paths", uploaded) + self.assertGreater(len(uploaded["paths"]), 0) diff --git a/tests/test_render_zenodo.py b/tests/test_render_zenodo.py new file mode 100644 index 0000000..c73647f --- /dev/null +++ b/tests/test_render_zenodo.py @@ -0,0 +1,88 @@ +# tests/test_render_zenodo.py +import tempfile +from pathlib import Path +from unittest import TestCase +from unittest.mock import patch + +from django.core.management import call_command +from publications.models import Publication, Source + + +class RenderZenodoTest(TestCase): + def setUp(self): + # Temp “project root” + self._tmpdir = tempfile.TemporaryDirectory() + self.project_root = Path(self._tmpdir.name) + self.templates_dir = self.project_root / "publications" / "templates" + self.cmds_dir = self.project_root / "publications" / "management" / "commands" + self.data_dir = self.project_root / "data" + self.templates_dir.mkdir(parents=True, exist_ok=True) + self.cmds_dir.mkdir(parents=True, exist_ok=True) + self.data_dir.mkdir(parents=True, exist_ok=True) + + # Minimal README template with Sources + (self.templates_dir / "README.md.j2").write_text( + "# OPTIMAP FAIR Data Package\n" + "**Version:** {{ version }}\n\n" + "## Sources\n\n" + "{% for src in sources %}- [{{ src.name }}]({{ src.url }})\n{% endfor %}\n" + "\n## Codebook\n\n" + "| Field | Description |\n|---|---|\n| id | pk |\n", + encoding="utf-8", + ) + + # DB fixtures + Publication.objects.create(title="A", publicationDate="2010-10-10") + + # Bad labels to clean + Source.objects.create(name="2000", url_field="https://optimap.science") # numeric-only -> OPTIMAP + Source.objects.create(name="", url_field="https://example.org") # blank -> domain label + Source.objects.create(name=" ", url_field="https://example.org") # duplicate -> dedupe + + # Good label + Source.objects.create( + name="AGILE: GIScience Series", + url_field="https://agile-giss.copernicus.org" + ) + + # Import after DB is ready + import importlib + self.render_mod = importlib.import_module( + "publications.management.commands.render_zenodo" + ) + + # Fake Path so parents[3] stays inside tmp root + class FakePath(Path): + _flavour = Path(".")._flavour + def resolve(self): + return self + self.FakePath = FakePath + self.render_file = str(self.cmds_dir / "render_zenodo.py") + + def tearDown(self): + self._tmpdir.cleanup() + + def test_render_produces_clean_readme_and_assets(self): + # Don’t actually run `git archive` + def _noop(*a, **k): return None + + with patch.object(self.render_mod, "__file__", new=self.render_file), \ + patch.object(self.render_mod, "Path", self.FakePath), \ + patch("subprocess.run", _noop): + call_command("render_zenodo") + + readme_path = self.data_dir / "README.md" + zip_path = self.data_dir / "optimap-main.zip" + dyn_path = self.data_dir / "zenodo_dynamic.json" + + self.assertTrue(readme_path.exists(), "README.md not generated") + self.assertTrue(zip_path.exists(), "optimap-main.zip not generated") + self.assertTrue(dyn_path.exists(), "zenodo_dynamic.json not generated") + + md = readme_path.read_text(encoding="utf-8") + # Sources cleanup assertions + self.assertNotIn("- [2000](", md, "Numeric-only label leaked into Sources") + self.assertIn("- [OPTIMAP](https://optimap.science)", md, "OPTIMAP override missing") + self.assertIn("AGILE: GIScience Series", md, "Named source missing") + # example.org should appear only once after dedupe + self.assertEqual(md.count("example.org"), 1, "Duplicate source/domain not deduped")