mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 22:27:13 +01:00
The three Python packages pinned ruff twice at different versions: requirements.txt at 0.16.7, .pre-commit-config.yaml at v0.15.2. Only the older one ever ran, so `select = ["ALL"]` was enforced two minor versions behind. Measured before aligning: v0.15.2 reports 0 findings, every 0.16.x reports CPY001 on each file -- 70 crdt-sync, 23 freedays, 71 gatelock, and no other rule. Resolved by adding the notice rather than ignoring the rule, so the gate keeps its teeth. Two gatelock modules sat at exactly the 250-line cap, so a one-line header tipped them to 251. Both are split along a real seam rather than shaved: _widget_fills.py colour arithmetic over LockConfig, no tkinter import _surfaces_focus.py selects among existing surfaces, never mutates them The focus split keeps _surfaces.py's documented containment (it stays the only module that creates, moves or destroys windows), and the static guard in test_recovery_invariants.py globs _surfaces*.py, so the new sibling is covered with no list to extend. widgets.py re-exports the fills helpers, which keeps gatelock.widgets the single public surface its tests import. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019kMvSMtsVMB3d2uoZ56pbp
141 lines
4.6 KiB
Python
141 lines
4.6 KiB
Python
# Copyright (c) 2026 Krzysztof Rudnicki
|
|
"""Tests for the shared Firebase configuration loader.
|
|
|
|
Every failure mode here is one that would otherwise surface as an
|
|
authentication error long after the real mistake (a placeholder left in, a
|
|
newline a text editor appended), so each is asserted to name the field at
|
|
fault rather than merely to raise.
|
|
"""
|
|
|
|
# These tests assert on private attributes on purpose: `_timeout_seconds`
|
|
# and `_path` are exactly what the fixes under test set, and there is no
|
|
# public accessor for either.
|
|
# pylint: disable=protected-access
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import TYPE_CHECKING
|
|
|
|
import pytest
|
|
|
|
from crdt_sync import (
|
|
ConfigError,
|
|
FirebaseConfig,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
_VALID = {
|
|
"apiKey": "AIzaSyExample",
|
|
"databaseUrl": "https://kuhy-syncs-default-rtdb.europe-west1.firebasedatabase.app",
|
|
"projectId": "kuhy-syncs",
|
|
"uid": "OvA2REQyLIhAHOEjzwS1o877rgG3",
|
|
"email": "sync@example.com",
|
|
}
|
|
|
|
|
|
class _StubRemote:
|
|
"""A do-nothing :class:`~crdt_sync.RemoteStore`, standing in for GitHub.
|
|
|
|
A real implementation rather than a bare ``object()`` so the type checker
|
|
accepts it without a suppression.
|
|
"""
|
|
|
|
def list_directory(self, _path: str) -> list[str]:
|
|
"""Report an empty remote directory."""
|
|
return []
|
|
|
|
def get_file_text(self, _path: str) -> str | None:
|
|
"""Report the file as absent."""
|
|
return None
|
|
|
|
def put_file_text(self, path: str, text: str, *, message: str) -> None:
|
|
"""Accept and discard a write."""
|
|
|
|
def delete_file(self, path: str, *, message: str = "") -> None:
|
|
"""Accept and discard a delete."""
|
|
|
|
def can_access_remote(self) -> bool:
|
|
"""Report the remote as reachable."""
|
|
return True
|
|
|
|
|
|
def _write(path: Path, data: object) -> Path:
|
|
"""Write ``data`` as JSON to ``path`` and return it."""
|
|
path.write_text(json.dumps(data), encoding="utf-8")
|
|
return path
|
|
|
|
|
|
def test_loads_a_valid_config(tmp_path: Path) -> None:
|
|
"""Loads a valid config."""
|
|
config = FirebaseConfig.load(_write(tmp_path / "firebase.json", _VALID))
|
|
|
|
assert config.api_key == "AIzaSyExample"
|
|
assert config.project_id == "kuhy-syncs"
|
|
assert config.uid == "OvA2REQyLIhAHOEjzwS1o877rgG3"
|
|
assert config.email == "sync@example.com"
|
|
assert config.database_url.endswith("europe-west1.firebasedatabase.app")
|
|
|
|
|
|
def test_ignores_the_scaffold_comment_keys(tmp_path: Path) -> None:
|
|
"""The shipped scaffold explains each field inline; that is not config."""
|
|
annotated = {**_VALID, "_comment_apiKey": "where to find it", "_comment": "x"}
|
|
|
|
config = FirebaseConfig.load(_write(tmp_path / "firebase.json", annotated))
|
|
|
|
assert config.api_key == "AIzaSyExample"
|
|
|
|
|
|
def test_reports_a_missing_file(tmp_path: Path) -> None:
|
|
"""Reports a missing file."""
|
|
with pytest.raises(ConfigError, match="does not exist"):
|
|
FirebaseConfig.load(tmp_path / "absent.json")
|
|
|
|
|
|
def test_reports_unreadable_json(tmp_path: Path) -> None:
|
|
"""Reports unreadable JSON."""
|
|
path = tmp_path / "firebase.json"
|
|
path.write_text("{not json", encoding="utf-8")
|
|
|
|
with pytest.raises(ConfigError, match="not valid JSON"):
|
|
FirebaseConfig.load(path)
|
|
|
|
|
|
def test_reports_a_non_object_document(tmp_path: Path) -> None:
|
|
"""Reports a non object document."""
|
|
with pytest.raises(ConfigError, match="must contain a JSON object"):
|
|
FirebaseConfig.load(_write(tmp_path / "firebase.json", ["a list"]))
|
|
|
|
|
|
def test_reports_an_unreadable_path(tmp_path: Path) -> None:
|
|
"""A directory where a file belongs is an OSError, not a missing file."""
|
|
(tmp_path / "firebase.json").mkdir()
|
|
|
|
with pytest.raises(ConfigError, match="could not be read"):
|
|
FirebaseConfig.load(tmp_path / "firebase.json")
|
|
|
|
|
|
@pytest.mark.parametrize("field", sorted(_VALID))
|
|
def test_names_a_missing_field(tmp_path: Path, field: str) -> None:
|
|
"""Names a missing field."""
|
|
incomplete = {k: v for k, v in _VALID.items() if k != field}
|
|
|
|
with pytest.raises(ConfigError, match=field):
|
|
FirebaseConfig.load(_write(tmp_path / "firebase.json", incomplete))
|
|
|
|
|
|
def test_names_an_empty_field(tmp_path: Path) -> None:
|
|
"""Names an empty field."""
|
|
with pytest.raises(ConfigError, match="uid"):
|
|
FirebaseConfig.load(_write(tmp_path / "firebase.json", {**_VALID, "uid": " "}))
|
|
|
|
|
|
def test_names_an_unfilled_placeholder(tmp_path: Path) -> None:
|
|
"""The exact state the scaffold ships in must not load silently."""
|
|
unfilled = {**_VALID, "apiKey": "PASTE_WEB_API_KEY_HERE"}
|
|
|
|
with pytest.raises(ConfigError, match=r"placeholder for: apiKey"):
|
|
FirebaseConfig.load(_write(tmp_path / "firebase.json", unfilled))
|