diff --git a/CHANGES.md b/CHANGES.md index 9cc2133..cd49af2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -59,7 +59,7 @@ concern - readable here, never shipped as something to parse. --- -## 7.1.0-beta.23 - 2026-09-26 - network: property defined; sync, publish and upstream merge marked networked +## 7.1.0-beta.24 - 2026-09-26 - log append: unreadable --body-file is an ERROR line, not a traceback **Author:** Torben Nehmer @@ -92,6 +92,7 @@ concern - readable here, never shipped as something to parse. - Command records, Instance health group: one bullet per check, examples - Command records: NOTES is always a tuple of bullets; every record's examples are tested - network: property defined; sync, publish and upstream merge marked networked +- log append: unreadable --body-file is an ERROR line, not a traceback ### CalDAV task-tracker provider (Nextcloud Tasks, iOS Reminders); review reports unknown-value findings instead of skipping them @@ -376,6 +377,18 @@ is gone from `version check`'s record, from two docstrings in `version_cmd.py`, CalDAV and Super Productivity providers do too. A test over the real registry pins the set of `network: yes` commands, so a change to it is an edit to that test rather than silent drift. +### log append: unreadable --body-file is an ERROR line, not a traceback + +`log append --body-file ` rief `Path.read_text()` ungeschützt auf; fehlte die Datei, war +sie ein Verzeichnis oder kein gültiges UTF-8, endete der Aufruf mit einem Python-Traceback statt +mit der `ERROR`-Zeile, die der Datensatz seit jeher versprach - Fall 4 des Tool error contract +(unerwarteter Fehler, nicht wiederholen) statt Fall 2 (Validierungsfehler, einmal korrigiert +wiederholen), obwohl der Datensatz Fall 2 dokumentierte. Das Lesen ist jetzt in +`try/except (OSError, UnicodeDecodeError)` gefasst und bricht über `fail()` ab, bevor +`kb/log.md` geöffnet wird; die Datei bleibt in jedem Fehlerfall byte-identisch zum Vorher. Der +Datensatz benennt die Ursache jetzt genauer ("missing, not a readable file, or not valid +UTF-8"). Gefunden beim Datensatz-Durchgang für #142/#143 (Gitea #145). + --- ## 7.0.0 - 2026-09-22 - Task-Tracker-Anbindung: Vorhaben als Seitenart, Verpflichtungsschicht, Weekly Review als Read-Time-Join diff --git a/VERSION b/VERSION index 867ee81..f95e060 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.1.0-beta.23 +7.1.0-beta.24 diff --git a/tools/CONTRACT.md b/tools/CONTRACT.md index 67eb548..6f1dd2b 100644 --- a/tools/CONTRACT.md +++ b/tools/CONTRACT.md @@ -999,12 +999,12 @@ Append a formatted entry to `kb/log.md`. - 0 success - 1 `--op` is not one of ingest, query, lint, create, update, delete, rename, move -- 1 `--body-file` cannot be read +- 1 `--body-file` is missing, not a readable file, or not valid UTF-8 **ON FAILURE** - `--op` is not one of ingest, query, lint, create, update, delete, rename, move -> Nothing was written - fix the argument and retry once -- `--body-file` cannot be read -> Nothing was written - fix the path and retry once +- `--body-file` is missing, not a readable file, or not valid UTF-8 -> Nothing was written - fix the path and retry once **NEVER** diff --git a/tools/chemenu/commands/log_append.py b/tools/chemenu/commands/log_append.py index 2a7642d..3904e1f 100644 --- a/tools/chemenu/commands/log_append.py +++ b/tools/chemenu/commands/log_append.py @@ -75,7 +75,7 @@ def ingests_since_last_lint(entries: list[tuple[str, str, str]]) -> int: reaction="Nothing was written - fix the argument and retry once", ), cli_contract.Failure( - cause="`--body-file` cannot be read", + cause="`--body-file` is missing, not a readable file, or not valid UTF-8", reaction="Nothing was written - fix the path and retry once", ), ), @@ -104,7 +104,10 @@ def log_append( fail(f"--op must be one of {VALID_OPS}") text = body if body_file: - text = body_file.read_text(encoding="utf-8") + try: + text = body_file.read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError) as exc: + fail(f"Cannot read --body-file {body_file}: {exc}") entry = format_log_entry(op, title, text) with config.LOG_FILE.open("a", encoding="utf-8") as f: f.write("\n" + entry) diff --git a/tools/chemenu/tests/test_log_append.py b/tools/chemenu/tests/test_log_append.py index ee77f48..b164766 100644 --- a/tools/chemenu/tests/test_log_append.py +++ b/tools/chemenu/tests/test_log_append.py @@ -1,6 +1,10 @@ +import pytest +import typer + from chemenu.commands.log_append import ( format_log_entry, ingests_since_last_lint, + log_append, log_status, parse_log_entries, ) @@ -75,3 +79,60 @@ def test_log_status_warns_at_the_ten_ingest_threshold(tmp_path, monkeypatch, cap out = capsys.readouterr().out assert "Ingests since last lint: 10" in out assert "Threshold reached" in out + + +# --- log_append --body-file: unreadable is an ERROR line, never a traceback (Gitea #145) --- + + +def _make_missing(tmp_path): + return tmp_path / "nonexistent.md" + + +def _make_directory(tmp_path): + d = tmp_path / "a-directory" + d.mkdir() + return d + + +def _make_non_utf8(tmp_path): + p = tmp_path / "not-utf8.md" + p.write_bytes(b"\xff\xfe garbage") + return p + + +@pytest.mark.parametrize( + "make_bad_file", + [_make_missing, _make_directory, _make_non_utf8], + ids=["missing", "directory", "non-utf8"], +) +def test_log_append_reports_an_unreadable_body_file_as_an_error(make_bad_file, tmp_path, monkeypatch, capsys): + import chemenu.config as config + + log_file = tmp_path / "log.md" + before = "# Wiki Log\n" + log_file.write_text(before, encoding="utf-8") + monkeypatch.setattr(config, "LOG_FILE", log_file) + bad_file = make_bad_file(tmp_path) + + with pytest.raises(typer.Exit) as exc: + log_append(op="ingest", title="raw/notes/a.md", body="", body_file=bad_file) + assert exc.value.exit_code == 1 + + out = capsys.readouterr().out + assert out.startswith("ERROR ") + assert str(bad_file) in out + assert log_file.read_text(encoding="utf-8") == before + + +def test_log_append_reads_the_body_from_body_file(tmp_path, monkeypatch): + import chemenu.config as config + + log_file = tmp_path / "log.md" + log_file.write_text("# Wiki Log\n", encoding="utf-8") + monkeypatch.setattr(config, "LOG_FILE", log_file) + body_file = tmp_path / "body.md" + body_file.write_text("Created [[Docker]].", encoding="utf-8") + + log_append(op="ingest", title="raw/notes/a.md", body="", body_file=body_file) + + assert "Created [[Docker]]." in log_file.read_text(encoding="utf-8")