screen-locker/screen_locker/tests/test_credential_recovery.py
Krzysztof kuhy Rudnicki 4a36b6b264 Fix mechanical pylint findings in tests
Booleaness asserts, hoisted imports, fixture(name=...) instead of
shadowing arguments, explicit encodings, snake_case names, comparison
of bound methods, dead protected-access disables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWYdwRAtMg7ki6TKxuU
2026-11-01 21:11:34 +01:00

163 lines
6.0 KiB
Python

"""A dead Firebase credential must heal itself, not wait for a human.
On 2026-08-24 the PC could not read Firebase because ``screen_locker`` had no
cached session and the shared password no longer authenticated. Every sibling
app on the same machine -- ``diet_guard``, ``wake_alarm``, ``todo`` -- held a
live refresh token for the *same* account and project the whole time. The
credential was on disk, three directories away, and nothing looked for it.
Recovery is therefore local and needs no human: borrow a sibling's refresh
token, mint a session, and cache it under our own app name.
"""
from __future__ import annotations
import json
from typing import TYPE_CHECKING
from screen_locker._credential_recovery import (
find_sibling_refresh_tokens,
recover_session,
)
from screen_locker.tests._credential_fixtures import write_cache as _write_cache
if TYPE_CHECKING:
from pathlib import Path
import pytest
class TestFindSiblingRefreshToken:
"""Locating a live credential already present on this machine."""
def test_finds_a_sibling_apps_token(self, tmp_path: Path) -> None:
"""Any sibling app's cache is a valid source -- one account, one project."""
_write_cache(
tmp_path / "diet_guard" / "firebase_auth.json",
"refresh-abc",
expires_at="2026-08-24T12:00:00+00:00",
)
found = find_sibling_refresh_tokens(tmp_path, skip="screen_locker")
assert found == [("diet_guard", "refresh-abc")]
def test_ignores_our_own_stale_cache(self, tmp_path: Path) -> None:
"""Our own dead cache is the thing being replaced, never the source."""
_write_cache(
tmp_path / "screen_locker" / "firebase_auth.json",
"refresh-ours",
expires_at="2026-08-24T12:00:00+00:00",
)
assert not find_sibling_refresh_tokens(tmp_path, skip="screen_locker")
def test_skips_unreadable_and_malformed_caches(self, tmp_path: Path) -> None:
"""A corrupt sibling must not abort the search over the healthy ones."""
bad = tmp_path / "broken_app" / "firebase_auth.json"
bad.parent.mkdir(parents=True)
bad.write_text("{not json")
_write_cache(
tmp_path / "wake_alarm" / "firebase_auth.json",
"refresh-xyz",
expires_at="2026-08-24T12:00:00+00:00",
)
found = find_sibling_refresh_tokens(tmp_path, skip="screen_locker")
assert found == [("wake_alarm", "refresh-xyz")]
def test_reports_nothing_when_no_sibling_has_one(self, tmp_path: Path) -> None:
"""No source is a real outcome; the caller must hear it, not guess."""
assert not find_sibling_refresh_tokens(tmp_path, skip="screen_locker")
def test_missing_config_root_is_not_an_error(self, tmp_path: Path) -> None:
"""A machine with no ~/.config yet has no donor -- and must not crash.
Recovery runs unattended inside a lock decision, so an exception here
would take the screen for a reason that has nothing to do with
working out.
"""
assert not find_sibling_refresh_tokens(tmp_path / "nope", skip="screen_locker")
def test_cache_without_a_usable_token_is_skipped(self, tmp_path: Path) -> None:
"""Readable JSON is not the same as a usable credential.
A sibling mid-logout leaves a well-formed cache whose refresh_token is
missing or empty. Returning that would mint nothing and report success.
"""
for app, payload in (
("no_token_app", {"expires_at": "2026-08-24T12:00:00+00:00"}),
("empty_token_app", {"refresh_token": ""}),
("wrong_type_app", {"refresh_token": 12345}),
):
cache = tmp_path / app / "firebase_auth.json"
cache.parent.mkdir(parents=True)
cache.write_text(json.dumps(payload))
_write_cache(
tmp_path / "zz_healthy" / "firebase_auth.json",
"refresh-good",
expires_at="2026-08-24T12:00:00+00:00",
)
found = find_sibling_refresh_tokens(tmp_path, skip="screen_locker")
assert found == [("zz_healthy", "refresh-good")]
class TestRecoverSession:
"""Turning a borrowed refresh token into our own cached session."""
def test_recovers_and_reports_the_donor(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A successful recovery must say where the credential came from."""
_write_cache(
tmp_path / "diet_guard" / "firebase_auth.json",
"refresh-abc",
expires_at="2026-08-24T12:00:00+00:00",
)
minted: list[str] = []
def _mint(refresh_token: str) -> None:
minted.append(refresh_token)
result = recover_session(
config_root=tmp_path, app_name="screen_locker", mint=_mint
)
assert result.recovered is True
assert result.donor == "diet_guard"
assert minted == ["refresh-abc"]
def test_failure_explains_itself(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A failed recovery must carry a reason a human can act on."""
def _mint(_refresh_token: str) -> None:
message = "TOKEN_EXPIRED"
raise RuntimeError(message)
_write_cache(
tmp_path / "diet_guard" / "firebase_auth.json",
"refresh-abc",
expires_at="2026-08-24T12:00:00+00:00",
)
result = recover_session(
config_root=tmp_path, app_name="screen_locker", mint=_mint
)
assert result.recovered is False
assert "TOKEN_EXPIRED" in result.reason
def test_no_donor_is_reported_not_swallowed(self, tmp_path: Path) -> None:
""" "Nothing to borrow" must be a stated reason, never a silent False."""
result = recover_session(
config_root=tmp_path, app_name="screen_locker", mint=lambda _t: None
)
assert result.recovered is False
assert "no sibling" in result.reason.lower()