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
235 lines
7.6 KiB
Python
235 lines
7.6 KiB
Python
# Copyright (c) 2026 Krzysztof Rudnicki
|
|
"""Tests for building a sync client from a config file.
|
|
|
|
Split from ``test_config.py`` (250-line cap);
|
|
``test_config_credentials.py`` keeps reading and validating the password.
|
|
"""
|
|
|
|
# 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
|
|
|
|
from crdt_sync import (
|
|
FirebaseConfig,
|
|
FirebaseSyncClient,
|
|
credential_store_for,
|
|
mirror_client_for,
|
|
)
|
|
from crdt_sync._config import _DEFAULT_TIMEOUT_SECONDS, firebase_client_for
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
_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_credential_store_is_per_app() -> None:
|
|
"""Two apps must not share one cache file; concurrent writes corrupt it."""
|
|
diet = credential_store_for("diet_guard")
|
|
alarm = credential_store_for("wake_alarm")
|
|
|
|
assert diet._path != alarm._path
|
|
assert diet._path.name == "firebase_auth.json"
|
|
assert "diet_guard" in str(diet._path)
|
|
|
|
|
|
def test_client_signs_in_only_without_a_cached_session(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""The common path is a cached refresh token, costing no round trip."""
|
|
config = FirebaseConfig.load(_write(tmp_path / "firebase.json", _VALID))
|
|
sign_ins: list[tuple[str, str]] = []
|
|
|
|
class _Auth:
|
|
def __init__(self, api_key: str, _store: object) -> None:
|
|
self.api_key = api_key
|
|
|
|
def has_session(self) -> bool:
|
|
"""Pretend a cached session exists."""
|
|
return True
|
|
|
|
def sign_in(self, email: str, password: str) -> None:
|
|
"""Record the sign-in attempt."""
|
|
sign_ins.append((email, password)) # pragma: no cover - must not run
|
|
|
|
monkeypatch.setattr("crdt_sync._config.FirebaseTokenProvider", _Auth)
|
|
client = firebase_client_for("diet_guard", config=config)
|
|
|
|
assert not sign_ins
|
|
assert client is not None
|
|
|
|
|
|
def test_client_signs_in_when_there_is_no_session(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Client signs in when there is no session."""
|
|
config = FirebaseConfig.load(_write(tmp_path / "firebase.json", _VALID))
|
|
password = tmp_path / "password"
|
|
password.write_text("hunter2", encoding="utf-8")
|
|
monkeypatch.setattr("crdt_sync._config.PASSWORD_FILE", password)
|
|
sign_ins: list[tuple[str, str]] = []
|
|
|
|
class _Auth:
|
|
def __init__(self, api_key: str, _store: object) -> None:
|
|
self.api_key = api_key
|
|
|
|
def has_session(self) -> bool:
|
|
"""Pretend no cached session exists."""
|
|
return False
|
|
|
|
def sign_in(self, email: str, password_value: str) -> None:
|
|
"""Record the credentials the caller signed in with."""
|
|
sign_ins.append((email, password_value))
|
|
|
|
monkeypatch.setattr("crdt_sync._config.FirebaseTokenProvider", _Auth)
|
|
firebase_client_for("diet_guard", config=config)
|
|
|
|
assert sign_ins == [("sync@example.com", "hunter2")]
|
|
|
|
|
|
def test_mirror_client_makes_firebase_primary(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Firebase authoritative, GitHub best-effort -- the cutover shape."""
|
|
config = FirebaseConfig.load(_write(tmp_path / "firebase.json", _VALID))
|
|
github = _StubRemote()
|
|
|
|
class _Auth:
|
|
def __init__(self, api_key: str, _store: object) -> None:
|
|
self.api_key = api_key
|
|
|
|
def has_session(self) -> bool:
|
|
"""Pretend a cached session exists."""
|
|
return True
|
|
|
|
def sign_in(self, email: str, password: str) -> None:
|
|
"""Record the sign-in attempt."""
|
|
raise AssertionError # pragma: no cover - must not run
|
|
|
|
monkeypatch.setattr("crdt_sync._config.FirebaseTokenProvider", _Auth)
|
|
client = mirror_client_for("diet_guard", github, config=config)
|
|
|
|
# Reaching into the wrapper is the point: which backend is authoritative
|
|
# is the one thing a rollback depends on getting right.
|
|
assert client.mirror is github
|
|
assert isinstance(client.primary, FirebaseSyncClient)
|
|
|
|
|
|
def test_client_loads_the_shared_config_when_none_is_given(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""The default path: apps name themselves and nothing else."""
|
|
monkeypatch.setattr(
|
|
"crdt_sync._config.CONFIG_FILE",
|
|
_write(tmp_path / "firebase.json", _VALID),
|
|
)
|
|
|
|
class _Auth:
|
|
def __init__(self, api_key: str, _store: object) -> None:
|
|
self.api_key = api_key
|
|
|
|
def has_session(self) -> bool:
|
|
"""Pretend a cached session exists."""
|
|
return True
|
|
|
|
def sign_in(self, email: str, password: str) -> None:
|
|
"""Record the sign-in attempt."""
|
|
raise AssertionError # pragma: no cover - must not run
|
|
|
|
monkeypatch.setattr("crdt_sync._config.FirebaseTokenProvider", _Auth)
|
|
|
|
assert firebase_client_for("wake_alarm") is not None
|
|
|
|
|
|
def _client_with_stub_auth(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
**kwargs: float,
|
|
) -> FirebaseSyncClient:
|
|
"""Return a client built with stubbed auth, forwarding ``kwargs``."""
|
|
config = FirebaseConfig.load(_write(tmp_path / "firebase.json", _VALID))
|
|
|
|
class _Auth:
|
|
def __init__(self, api_key: str, _store: object) -> None:
|
|
"""Record the key; the credential store is irrelevant here."""
|
|
self.api_key = api_key
|
|
|
|
def has_session(self) -> bool:
|
|
"""Pretend a cached session exists, so no sign-in is attempted."""
|
|
return True
|
|
|
|
monkeypatch.setattr("crdt_sync._config.FirebaseTokenProvider", _Auth)
|
|
return firebase_client_for("diet_guard", config=config, **kwargs)
|
|
|
|
|
|
def test_client_uses_the_requested_timeout(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""A caller's timeout must reach the client, not just the GitHub half."""
|
|
# A keyword with a default adds no branch, so coverage alone cannot tell
|
|
# whether it is actually threaded through to the client.
|
|
client = _client_with_stub_auth(tmp_path, monkeypatch, timeout_seconds=3)
|
|
|
|
assert client._timeout_seconds == 3
|
|
|
|
|
|
def test_client_timeout_defaults_to_the_shared_value(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Omitting the keyword keeps the library-wide default."""
|
|
client = _client_with_stub_auth(tmp_path, monkeypatch)
|
|
|
|
assert client._timeout_seconds == _DEFAULT_TIMEOUT_SECONDS
|