utils/crdt-sync/crdt_sync/tests/test_config_client.py
Krzysztof kuhy Rudnicki 6154dc09bf Pin pre-commit's ruff to 0.16.7 and satisfy the CPY001 it brings
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
2026-09-12 18:11:24 +02:00

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