"""Tests for `openrun.ingest.manual` — CSV import + idempotency.""" from __future__ import annotations from pathlib import Path import pytest from openrun.ingest.manual import import_csv def _write_csv(tmp_path: Path, text: str) -> Path: p = tmp_path / "manual.csv" p.write_text(text) return p def test_import_csv_inserts_required_columns(tmp_conn, tmp_path: Path) -> None: csv = _write_csv(tmp_path, """\ activity_date,activity_type,distance_km,duration_min,training_load,notes 2026-05-15,strength,,45,30,upper body 2026-05-16,hike,8.0,120,40,morning hike """) result = import_csv(tmp_conn, csv) assert result.inserted == 2 assert result.updated == 0 assert result.skipped == 0 assert result.errors == [] rows = tmp_conn.execute( "SELECT activity_date, activity_type, distance_km, duration_min, training_load, notes " "FROM manual_activities ORDER BY activity_date" ).fetchall() assert rows[0]["activity_date"] == "2026-05-15" assert rows[0]["activity_type"] == "strength" assert rows[0]["distance_km"] is None assert rows[0]["duration_min"] == 45 assert rows[0]["training_load"] == 30 assert rows[0]["notes"] == "upper body" assert rows[1]["distance_km"] == 8.0 def test_import_csv_requires_header_columns(tmp_conn, tmp_path: Path) -> None: csv = _write_csv(tmp_path, "date,type\n2026-05-15,strength\n") # wrong column names with pytest.raises(ValueError, match="missing required columns"): import_csv(tmp_conn, csv) def test_import_csv_external_id_makes_reimport_idempotent(tmp_conn, tmp_path: Path) -> None: csv = _write_csv(tmp_path, """\ activity_date,activity_type,training_load,external_id 2026-05-15,strength,30,strava-12345 """) import_csv(tmp_conn, csv) second = import_csv(tmp_conn, csv) assert second.inserted == 0 assert second.updated == 1 count = tmp_conn.execute("SELECT COUNT(*) FROM manual_activities").fetchone()[0] assert count == 1 def test_import_csv_reimport_with_new_tl_updates_value(tmp_conn, tmp_path: Path) -> None: """External_id upsert should reflect changed training_load on re-import.""" csv1 = _write_csv(tmp_path, """\ activity_date,activity_type,training_load,external_id 2026-05-15,strength,30,k1 """) import_csv(tmp_conn, csv1) csv2 = _write_csv(tmp_path, """\ activity_date,activity_type,training_load,external_id 2026-05-15,strength,55,k1 """) import_csv(tmp_conn, csv2) tl = tmp_conn.execute("SELECT training_load FROM manual_activities WHERE external_id='k1'").fetchone()[0] assert tl == 55 def test_import_csv_blank_external_id_allows_duplicates(tmp_conn, tmp_path: Path) -> None: """No external_id = caller is intentionally logging without dedupe key. Two imports = two rows.""" csv = _write_csv(tmp_path, """\ activity_date,activity_type,training_load 2026-05-15,strength,30 """) import_csv(tmp_conn, csv) import_csv(tmp_conn, csv) assert tmp_conn.execute("SELECT COUNT(*) FROM manual_activities").fetchone()[0] == 2 def test_import_csv_skips_bad_rows_but_keeps_going(tmp_conn, tmp_path: Path) -> None: csv = _write_csv(tmp_path, """\ activity_date,activity_type,training_load 2026-05-15,strength,30 bad-date,hike,40 2026-05-17,run, """) result = import_csv(tmp_conn, csv) assert result.inserted == 2 assert result.skipped == 1 assert len(result.errors) == 1 assert "line 3" in result.errors[0] def test_import_csv_accepts_iso_datetime_in_date_column(tmp_conn, tmp_path: Path) -> None: csv = _write_csv(tmp_path, """\ activity_date,activity_type,training_load 2026-05-15T08:30:00,strength,30 """) import_csv(tmp_conn, csv) d = tmp_conn.execute("SELECT activity_date FROM manual_activities").fetchone()[0] assert d == "2026-05-15"