fix(telegram): make Windows-unmasked tests cross-platform

Guarding the fcntl import let Windows collection succeed, which surfaced 3
telegram tests that had never run on Windows — all test-portability bugs:
- log_streamer byte-count broke on CRLF -> fixture writes newline=''
- bot_registry write-failure used Unix-only /proc -> file-as-parent (all OS)
- validate_bot_config rejected POSIX work_dir on Windows (Path.is_absolute is
  host-dependent) -> test absoluteness under PurePosixPath OR PureWindowsPath
493 telegram tests green on Linux; ruff clean.
This commit is contained in:
AIOSAI
2026-06-25 03:44:26 -07:00
parent ec6e966137
commit 7e9c0cfca7
4 changed files with 28 additions and 13 deletions
+8 -1
View File
@@ -55,7 +55,14 @@ PyPI version — not the changelog header.
last several PRs. Guarded the import (`try/except ImportError → fcntl = None`,
the established hooks/daemon convention) and routed the three flock call-sites
through no-op-on-Windows `_lock`/`_unlock` helpers — advisory locking still
applies on POSIX, is skipped where unavailable. 246 telegram tests green.
applies on POSIX, is skipped where unavailable. Fixing collection then
*unmasked* three telegram tests that had never actually run on Windows, all
test-portability bugs (not product bugs): a log-streamer byte-count broke on
CRLF translation (fixture now writes `newline=""`); a registry write-failure
test used the Unix-only `/proc` path (now a cross-platform file-as-directory
parent); and `validate_bot_config` rejected valid POSIX `work_dir`s on Windows
because `Path.is_absolute()` is host-dependent (now tests POSIX *and* Windows
absoluteness). 493 telegram tests green.
- **prax-monitor service feedback loop** — the unit wrote its own stdout into
`system_logs/`, the very directory the monitor tails *and* @trigger watches,
@@ -1,5 +1,5 @@
# Standard library
from pathlib import Path
from pathlib import PurePosixPath, PureWindowsPath
from typing import Optional, List
# Logging
@@ -208,8 +208,11 @@ def validate_bot_config(config: object) -> tuple[bool, str]:
return False, "bot_token must be a string in format 'id:hash'"
if "work_dir" in config and config["work_dir"] is not None:
work_dir = Path(config["work_dir"])
if not work_dir.is_absolute():
# work_dir is a deployment-target (Linux) path. Test absoluteness under
# POSIX *and* Windows semantics so validation is platform-independent —
# a bare host Path().is_absolute() would reject "/home/..." on Windows.
work_dir = str(config["work_dir"])
if not (PurePosixPath(work_dir).is_absolute() or PureWindowsPath(work_dir).is_absolute()):
return False, "work_dir must be an absolute path"
if "allowed_user_ids" in config:
@@ -6,7 +6,6 @@ No external dependencies beyond pytest.
"""
import json
from pathlib import Path
import pytest
from apps.handlers import bot_registry # type: ignore[import-not-found]
@@ -191,11 +190,15 @@ class TestSaveRegistry:
assert result is True
assert bot_registry.REGISTRY_DIR.is_dir()
def test_returns_false_on_write_failure(self, monkeypatch):
def test_returns_false_on_write_failure(self, monkeypatch, tmp_path):
"""Should return False when writing fails (e.g. permission error)."""
# Point to an impossible path
monkeypatch.setattr(bot_registry, "REGISTRY_DIR", Path("/proc/nonexistent/impossible"))
monkeypatch.setattr(bot_registry, "REGISTRY_FILE", Path("/proc/nonexistent/impossible/_registry.json"))
# Put a regular file where a directory is expected. mkdir(parents=True)
# then fails on every OS (NotADirectoryError/FileExistsError), so this is
# cross-platform — unlike a hardcoded Unix-only path such as /proc/...
blocker = tmp_path / "blocker"
blocker.write_text("x", encoding="utf-8")
monkeypatch.setattr(bot_registry, "REGISTRY_DIR", blocker / "sub")
monkeypatch.setattr(bot_registry, "REGISTRY_FILE", blocker / "sub" / "_registry.json")
result = bot_registry.save_registry({"bots": {}, "metadata": {}})
@@ -62,11 +62,13 @@ def streamer(logs_dir, tmp_path):
@pytest.fixture
def streamer_with_files(logs_dir, tmp_path):
"""Create a LogStreamer after pre-populating matching and non-matching log files."""
# Create matching log files with content
(logs_dir / "api_main.log").write_text("line1\nline2\n", encoding="utf-8")
(logs_dir / "api_error.log").write_text("err1\n", encoding="utf-8")
# Create matching log files with content. newline="" disables newline
# translation so byte counts match len() on Windows too (\n stays 1 byte,
# not \r\n) — real log files are LF-terminated.
(logs_dir / "api_main.log").write_text("line1\nline2\n", encoding="utf-8", newline="")
(logs_dir / "api_error.log").write_text("err1\n", encoding="utf-8", newline="")
# Create a non-matching file (should be ignored)
(logs_dir / "trigger_main.log").write_text("other\n", encoding="utf-8")
(logs_dir / "trigger_main.log").write_text("other\n", encoding="utf-8", newline="")
return _make_streamer(logs_dir, tmp_path)