"""Tests for bincio.render.merge — sidecar edit overlay logic.""" import json import textwrap from pathlib import Path import pytest from bincio.render.merge import apply_sidecar, merge_all, parse_sidecar # ── parse_sidecar ───────────────────────────────────────────────────────────── def test_parse_sidecar_full(tmp_path): md = tmp_path / "act.md" md.write_text(textwrap.dedent("""\ --- title: "Ride to the coast" sport: cycling highlight: true private: false hide_stats: [cadence, power] gear: "Trek Domane" --- Great day out with Marco. """)) fm, body = parse_sidecar(md) assert fm["title"] == "Ride to the coast" assert fm["sport"] == "cycling" assert fm["highlight"] is True assert fm["private"] is False assert fm["hide_stats"] == ["cadence", "power"] assert fm["gear"] == "Trek Domane" assert body == "Great day out with Marco." def test_parse_sidecar_no_frontmatter(tmp_path): md = tmp_path / "act.md" md.write_text("Just a description, no frontmatter.\n") fm, body = parse_sidecar(md) assert fm == {} assert body == "Just a description, no frontmatter." def test_parse_sidecar_frontmatter_only(tmp_path): md = tmp_path / "act.md" md.write_text("---\ntitle: Solo spin\n---\n") fm, body = parse_sidecar(md) assert fm["title"] == "Solo spin" assert body == "" # ── apply_sidecar ───────────────────────────────────────────────────────────── BASE_DETAIL = { "id": "2024-01-01T080000Z-morning-ride", "title": "Morning Ride", "sport": "cycling", "started_at": "2024-01-01T08:00:00Z", "description": "Original description from Strava.", "privacy": "public", "gear": None, "custom": {}, } def test_apply_sidecar_title_and_sport(): fm = {"title": "Renamed", "sport": "running"} result = apply_sidecar(BASE_DETAIL, fm, "") assert result["title"] == "Renamed" assert result["sport"] == "running" # Original must be unchanged assert BASE_DETAIL["title"] == "Morning Ride" def test_apply_sidecar_body_becomes_description(): result = apply_sidecar(BASE_DETAIL, {}, "My **epic** ride.") assert result["description"] == "My **epic** ride." def test_apply_sidecar_body_takes_precedence_over_fm_description(): fm = {"description": "FM description"} result = apply_sidecar(BASE_DETAIL, fm, "Body description") assert result["description"] == "Body description" def test_apply_sidecar_private_flag(): result = apply_sidecar(BASE_DETAIL, {"private": True}, "") assert result["privacy"] == "private" def test_apply_sidecar_highlight(): result = apply_sidecar(BASE_DETAIL, {"highlight": True}, "") assert result["custom"]["highlight"] is True def test_apply_sidecar_hide_stats(): result = apply_sidecar(BASE_DETAIL, {"hide_stats": ["cadence", "power"]}, "") assert result["custom"]["hide_stats"] == ["cadence", "power"] def test_apply_sidecar_does_not_mutate_input(): fm = {"title": "New title", "highlight": True} original_custom = BASE_DETAIL["custom"] apply_sidecar(BASE_DETAIL, fm, "") assert BASE_DETAIL["title"] == "Morning Ride" assert BASE_DETAIL["custom"] is original_custom assert "highlight" not in original_custom # ── merge_all ───────────────────────────────────────────────────────────────── @pytest.fixture() def data_dir(tmp_path): acts = tmp_path / "activities" acts.mkdir() # Two activities for act_id, title, sport, started_at in [ ("2024-01-01T080000Z-morning-ride", "Morning Ride", "cycling", "2024-01-01T08:00:00Z"), ("2024-01-02T090000Z-easy-run", "Easy Run", "running", "2024-01-02T09:00:00Z"), ]: detail = { "id": act_id, "title": title, "sport": sport, "started_at": started_at, "description": "", "privacy": "public", "custom": {}, } (acts / f"{act_id}.json").write_text(json.dumps(detail)) # Index index = {"activities": [ {"id": "2024-01-01T080000Z-morning-ride", "title": "Morning Ride", "sport": "cycling", "started_at": "2024-01-01T08:00:00Z", "privacy": "public", "custom": {}}, {"id": "2024-01-02T090000Z-easy-run", "title": "Easy Run", "sport": "running", "started_at": "2024-01-02T09:00:00Z", "privacy": "public", "custom": {}}, ]} (tmp_path / "index.json").write_text(json.dumps(index)) return tmp_path def test_merge_all_no_sidecars(data_dir): n = merge_all(data_dir) assert n == 0 merged = data_dir / "_merged" assert merged.exists() # Unmodified files are symlinked detail_link = merged / "activities" / "2024-01-01T080000Z-morning-ride.json" assert detail_link.is_symlink() def test_merge_all_applies_sidecar(data_dir): edits = data_dir / "edits" edits.mkdir() (edits / "2024-01-01T080000Z-morning-ride.md").write_text( "---\ntitle: Epic Ride\nhighlight: true\n---\n\nWhat a day!" ) n = merge_all(data_dir) assert n == 1 merged_json = data_dir / "_merged" / "activities" / "2024-01-01T080000Z-morning-ride.json" assert not merged_json.is_symlink() data = json.loads(merged_json.read_text()) assert data["title"] == "Epic Ride" assert data["custom"]["highlight"] is True assert data["description"] == "What a day!" # Untouched activity is still a symlink run_link = data_dir / "_merged" / "activities" / "2024-01-02T090000Z-easy-run.json" assert run_link.is_symlink() def test_merge_all_private_filtered_from_index(data_dir): edits = data_dir / "edits" edits.mkdir() (edits / "2024-01-01T080000Z-morning-ride.md").write_text("---\nprivate: true\n---\n") merge_all(data_dir) index = json.loads((data_dir / "_merged" / "index.json").read_text()) ids = [a["id"] for a in index["activities"]] assert "2024-01-01T080000Z-morning-ride" not in ids assert "2024-01-02T090000Z-easy-run" in ids def test_merge_all_highlight_sorts_first(data_dir): edits = data_dir / "edits" edits.mkdir() # Highlight the older activity — it should appear first (edits / "2024-01-01T080000Z-morning-ride.md").write_text("---\nhighlight: true\n---\n") merge_all(data_dir) index = json.loads((data_dir / "_merged" / "index.json").read_text()) ids = [a["id"] for a in index["activities"]] assert ids[0] == "2024-01-01T080000Z-morning-ride" def test_merge_all_idempotent(data_dir): edits = data_dir / "edits" edits.mkdir() (edits / "2024-01-01T080000Z-morning-ride.md").write_text("---\ntitle: Renamed\n---\n") merge_all(data_dir) merge_all(data_dir) # second run should not error or double-apply data = json.loads( (data_dir / "_merged" / "activities" / "2024-01-01T080000Z-morning-ride.json").read_text() ) assert data["title"] == "Renamed"