mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-11 11:47:42 +01:00
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
163 lines
6.0 KiB
Python
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()
|