mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-10 20:27:13 +01:00
The PC push aborted before it ever tried Firebase whenever the GitHub PAT
was missing ("Workouts NOT synced: no sync token"), so once the PAT was lost
this machine published nothing although Firebase was configured and live.
The phone has not written the GitHub repo since 2026-08-21.
- sync_client() / sync_client_or_reason(): firebase_client_for("screen_locker")
only, proven with can_access_remote(), healed once from a sibling app's
session, else a warning naming what will not sync (push AND pull) plus a
recorded degraded source. No config -> warning, no fallback.
- _manual_push and scripts/tombstone_sync_record.py take their client from
sync_client_or_reason(); the push result carries the reason.
- Deleted: read_sync_token, remote_client, the mirror client, _push_outcome
(the half-landed-push case no longer exists), SYNC_TOKEN_FILE and
SYNC_REPO_OWNER/NAME, the sync_token test fixture. Status backend is
"firebase" or "none".
- crdt-sync pinned to v0.9.2 in pyproject, requirements.txt and
install_systemd.sh (the installer still said v0.9.0).
kuhyx/syncs/screen-locker-sync is left untouched as an archive; no code
reads it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018RHwVPyiiQMPh4M3JQwQku
108 lines
3.8 KiB
Python
108 lines
3.8 KiB
Python
"""Backend selection and the two public pulls, split from ``test_workout_sync``.
|
|
|
|
``sync_client`` decides WHICH backend is read; ``pull_synced_workout`` and
|
|
``pull_all_manual_records`` are what the locker actually calls.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from crdt_sync import (
|
|
ConfigError,
|
|
FirebaseAuthError,
|
|
FirebaseSyncError,
|
|
)
|
|
import pytest
|
|
|
|
from screen_locker import _sync_client, _workout_sync
|
|
from screen_locker.tests._workout_sync_fixtures import (
|
|
_firebase_config,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
|
|
class TestSyncClient:
|
|
"""Firebase is the only backend: configured, missing, or unusable."""
|
|
|
|
def test_returns_none_when_nothing_is_configured(self) -> None:
|
|
"""Returns none when nothing is configured."""
|
|
assert _workout_sync.sync_client() is None
|
|
|
|
def test_warns_when_nothing_is_configured(
|
|
self, caplog: pytest.LogCaptureFixture
|
|
) -> None:
|
|
"""The warning names BOTH directions that stop syncing."""
|
|
with caplog.at_level("WARNING"):
|
|
_workout_sync.sync_client()
|
|
assert "Sync is OFF" in caplog.text
|
|
assert "will NOT be pushed" in caplog.text
|
|
assert "NOT be pulled" in caplog.text
|
|
|
|
def test_reason_names_the_missing_config(self) -> None:
|
|
"""The reason is a sentence a caller can put into its own result."""
|
|
client, reason = _workout_sync.sync_client_or_reason()
|
|
assert client is None
|
|
assert reason.startswith("no Firebase config at ")
|
|
|
|
def test_never_builds_a_client_without_config(self) -> None:
|
|
"""An unconfigured machine must not reach the network at all."""
|
|
with patch.object(_sync_client, "firebase_client_for") as build:
|
|
_workout_sync.sync_client()
|
|
build.assert_not_called()
|
|
|
|
def test_uses_firebase_when_the_config_exists(
|
|
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""A configured machine syncs over Firebase -- no PAT anywhere."""
|
|
_firebase_config(_sync_client, tmp_path, monkeypatch)
|
|
firebase = MagicMock()
|
|
monkeypatch.setattr(
|
|
_sync_client, "firebase_client_for", lambda *_a, **_k: firebase
|
|
)
|
|
assert _workout_sync.sync_client() is firebase
|
|
assert _workout_sync.sync_client_or_reason() == (firebase, "")
|
|
|
|
@pytest.mark.parametrize(
|
|
"error",
|
|
[
|
|
ConfigError("no password"),
|
|
FirebaseAuthError("bad credentials"),
|
|
FirebaseSyncError("backend down"),
|
|
],
|
|
)
|
|
def test_returns_none_when_firebase_only_is_unusable(
|
|
self, error: Exception, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""No fallback exists: report it loudly, do not raise."""
|
|
_firebase_config(_sync_client, tmp_path, monkeypatch)
|
|
|
|
def _boom(*_args: object, **_kwargs: object) -> None:
|
|
raise error
|
|
|
|
monkeypatch.setattr(_sync_client, "firebase_client_for", _boom)
|
|
client, reason = _workout_sync.sync_client_or_reason()
|
|
assert client is None
|
|
assert reason.startswith("Firebase unusable: ")
|
|
|
|
def test_warns_when_firebase_only_is_unusable(
|
|
self,
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""Warns when firebase only is unusable."""
|
|
_firebase_config(_sync_client, tmp_path, monkeypatch)
|
|
|
|
def _boom(*_args: object, **_kwargs: object) -> None:
|
|
message = "backend down"
|
|
raise FirebaseSyncError(message)
|
|
|
|
monkeypatch.setattr(_sync_client, "firebase_client_for", _boom)
|
|
with caplog.at_level("WARNING"):
|
|
_workout_sync.sync_client()
|
|
assert "backend down" in caplog.text
|