"""Unit tests for history export parsing (exporting.history). The commit-message grammar is defined by message_builder (Section 15); these tests pin the parser against it. """ from __future__ import annotations from git_auto_tracker.exporting.history import ( HistoryRecord, parse_message, to_rows, ) _MESSAGE = """SCADA automatic change capture - 2026-09-12 08:00:03+02:00 Summary: Added: 1 Modified: 1 Deleted: 1 Renamed: 0 Changes: A ; Stations/PS101/pump2.xml ; DOMAIN\\jsmith ; 2026-09-12 07:41:22+02:00 M ; System/global.xml ; UNKNOWN ; 2026-09-12 07:45:01+02:00 D ; Stations/PS027/old-config.xml Tracker-Version: 1.0.0 Tracker-Run: 01M2ADPMSHSA2TZKVH6PSVDVA0 Tracker-Host: SCADA-SRV-01 Tracker-Changes: 3 """ class TestParseMessage: def test_full_message(self) -> None: record = parse_message(_MESSAGE, ";") assert record is not None assert record.collected_at == "2026-09-12 08:00:03+02:00" assert record.run_id == "01M2ADPMSHSA2TZKVH6PSVDVA0" assert record.host == "SCADA-SRV-01" assert len(record.rows) == 3 added = record.rows[0] assert added["letter"] == "A" assert added["path"] == "Stations/PS101/pump2.xml" assert added["author"] == "DOMAIN\\jsmith" assert added["mtime"] == "2026-09-12 07:41:22+02:00" modified = record.rows[1] assert modified["letter"] == "M" assert modified["author"] == "UNKNOWN" deleted = record.rows[2] assert deleted["letter"] == "D" assert deleted["path"] == "Stations/PS027/old-config.xml" assert deleted["author"] == "" # bare line (FR-013) def test_paths_with_semicolon_inside_are_not_broken(self) -> None: # A path containing ';' is ambiguous by design; the parser splits on # ' ; ' so a lone ';' inside a filename keeps the line intact here. message = _MESSAGE.replace( "Stations/PS101/pump2.xml", " Stations/PS101;backup.xml".strip() ) record = parse_message(message, ";") assert record is not None assert record.rows[0]["path"] == "Stations/PS101;backup.xml" def test_non_tracker_message_returns_none(self) -> None: assert parse_message("initial import\n", ";") is None assert parse_message("", ";") is None def test_custom_separator(self) -> None: message = _MESSAGE.replace(" ; ", " | ") record = parse_message(message, "|") assert record is not None assert len(record.rows) == 3 assert record.rows[0]["path"] == "Stations/PS101/pump2.xml" def test_wrong_separator_yields_no_rows_but_is_tracker_commit(self) -> None: # Written with ';', parsed with '|': recognized but not splittable. record = parse_message(_MESSAGE, "|") assert record is not None assert record.collected_at # tracker commit detected assert record.rows == [] # but no reliable rows def test_rename_line(self) -> None: message = _MESSAGE.replace( " D ; Stations/PS027/old-config.xml", " R ; Stations/PS027/old.xml -> Stations/PS027/new.xml", ) record = parse_message(message, ";") assert record is not None row = record.rows[2] assert row["letter"] == "R" assert row["previous_path"] == "Stations/PS027/old.xml" assert row["path"] == "Stations/PS027/new.xml" class TestToRows: def test_long_format(self) -> None: record = parse_message(_MESSAGE, ";") assert record is not None record.commit = "a1b2c3d" rows = to_rows([record]) assert len(rows) == 3 first = rows[0] assert first["commit"] == "a1b2c3d" assert first["run_id"] == "01M2ADPMSHSA2TZKVH6PSVDVA0" assert first["letter"] == "A" assert set(first) == { "commit", "collected_at", "run_id", "host", "letter", "path", "previous_path", "author", "mtime", } def test_empty(self) -> None: assert to_rows([]) == [] assert to_rows([HistoryRecord(commit="x")]) == []