mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-10 21:27:13 +01:00
Real one-line docstrings stating what each test pins down. Files that would pass 250 lines moved whole classes into _part2 siblings (sick_panel, earned, poke_server, shutdown_reading, manual_workout_dialog). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWYdwRAtMg7ki6TKxuU
222 lines
6.9 KiB
Python
222 lines
6.9 KiB
Python
"""Tests for the on-disk sync posture shown in the status view.
|
|
|
|
The point of this module is that it never touches the network, so every test
|
|
here drives it purely from files in ``tmp_path``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import patch
|
|
|
|
from screen_locker._sync_status import (
|
|
SyncStatus,
|
|
format_sync_line,
|
|
gather_sync_status,
|
|
)
|
|
from screen_locker.status_view import main
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
def _gather(
|
|
tmp_path: Path,
|
|
*,
|
|
state_file: Path | None = None,
|
|
firebase_config: Path | None = None,
|
|
) -> SyncStatus:
|
|
"""Gather status against files under ``tmp_path`` unless overridden."""
|
|
return gather_sync_status(
|
|
state_file=state_file or tmp_path / "sync_state.json",
|
|
firebase_config=firebase_config or tmp_path / "firebase.json",
|
|
)
|
|
|
|
|
|
class TestBackendDetection:
|
|
"""Which backend this machine is configured for, from files alone."""
|
|
|
|
def test_no_credentials_is_not_configured(self, tmp_path: Path) -> None:
|
|
"""No credentials is not configured."""
|
|
assert _gather(tmp_path).backend == "none"
|
|
|
|
def test_a_leftover_github_token_does_not_count(self, tmp_path: Path) -> None:
|
|
"""A stale GitHub PAT on disk is not a backend: Firebase is the only one."""
|
|
(tmp_path / "sync_token").write_text("tok")
|
|
|
|
assert _gather(tmp_path).backend == "none"
|
|
|
|
def test_firebase_config_is_the_backend(self, tmp_path: Path) -> None:
|
|
"""The shared Firebase config is what makes this machine configured."""
|
|
(tmp_path / "firebase.json").write_text("{}")
|
|
|
|
assert _gather(tmp_path).backend == "firebase"
|
|
|
|
|
|
class TestPushState:
|
|
"""What the revision cache says about pushes and peers."""
|
|
|
|
def test_no_state_file_means_never_pushed(self, tmp_path: Path) -> None:
|
|
"""No state file means never pushed."""
|
|
status = _gather(tmp_path)
|
|
|
|
assert status.pushed is False
|
|
assert not status.peer_count
|
|
assert status.last_push is None
|
|
|
|
def test_a_pushed_rev_counts_as_pushed(self, tmp_path: Path) -> None:
|
|
"""A pushed rev counts as pushed."""
|
|
(tmp_path / "sync_state.json").write_text(
|
|
json.dumps({"pushed_rev": "abc", "peer_revs": {"a": "1", "b": "2"}})
|
|
)
|
|
|
|
status = _gather(tmp_path)
|
|
|
|
assert status.pushed is True
|
|
assert status.peer_count == 2
|
|
assert status.last_push is not None
|
|
|
|
def test_a_corrupt_state_file_reads_as_nothing_known(self, tmp_path: Path) -> None:
|
|
"""A broken cache must not stop the status window from opening."""
|
|
(tmp_path / "sync_state.json").write_text("{not json")
|
|
|
|
status = _gather(tmp_path)
|
|
|
|
assert status.pushed is False
|
|
assert not status.peer_count
|
|
|
|
def test_a_non_object_state_file_reads_as_nothing_known(
|
|
self, tmp_path: Path
|
|
) -> None:
|
|
"""A state file holding a JSON list instead of an object reads as never pushed."""
|
|
(tmp_path / "sync_state.json").write_text("[]")
|
|
|
|
assert _gather(tmp_path).pushed is False
|
|
|
|
def test_a_non_dict_peer_revs_counts_as_no_peers(self, tmp_path: Path) -> None:
|
|
"""A non dict peer revs counts as no peers."""
|
|
(tmp_path / "sync_state.json").write_text(
|
|
json.dumps({"pushed_rev": "abc", "peer_revs": "not-a-map"})
|
|
)
|
|
|
|
assert not _gather(tmp_path).peer_count
|
|
|
|
def test_an_unstattable_state_file_has_no_timestamp(self, tmp_path: Path) -> None:
|
|
"""A path whose parent is a file cannot be stat'd; that is not fatal."""
|
|
blocker = tmp_path / "blocker"
|
|
blocker.write_text("not a directory")
|
|
|
|
status = _gather(tmp_path, state_file=blocker / "sync_state.json")
|
|
|
|
assert status.last_push is None
|
|
|
|
|
|
class TestHealthy:
|
|
"""Configured AND actually pushing -- the distinction that matters."""
|
|
|
|
def test_unconfigured_is_not_healthy(self) -> None:
|
|
"""Unconfigured is not healthy."""
|
|
status = SyncStatus(
|
|
device_id="u", backend="none", pushed=False, peer_count=0, last_push=None
|
|
)
|
|
|
|
assert status.healthy is False
|
|
|
|
def test_configured_but_never_pushed_is_not_healthy(self) -> None:
|
|
"""The interesting failure: looks fine everywhere, produces no data."""
|
|
status = SyncStatus(
|
|
device_id="u",
|
|
backend="firebase",
|
|
pushed=False,
|
|
peer_count=0,
|
|
last_push=None,
|
|
)
|
|
|
|
assert status.healthy is False
|
|
|
|
def test_configured_and_pushed_is_healthy(self) -> None:
|
|
"""Configured and pushed is healthy."""
|
|
status = SyncStatus(
|
|
device_id="u", backend="firebase", pushed=True, peer_count=1, last_push="x"
|
|
)
|
|
|
|
assert status.healthy is True
|
|
|
|
|
|
class TestFormatSyncLine:
|
|
"""The one-line rendering used by the window and i3blocks."""
|
|
|
|
def test_unconfigured_says_so(self) -> None:
|
|
"""Unconfigured says so."""
|
|
status = SyncStatus(
|
|
device_id="u", backend="none", pushed=False, peer_count=0, last_push=None
|
|
)
|
|
|
|
assert format_sync_line(status) == "sync not configured"
|
|
|
|
def test_never_pushed_names_the_backend(self) -> None:
|
|
"""Never pushed names the backend."""
|
|
status = SyncStatus(
|
|
device_id="u",
|
|
backend="firebase",
|
|
pushed=False,
|
|
peer_count=0,
|
|
last_push=None,
|
|
)
|
|
|
|
assert "never pushed" in format_sync_line(status)
|
|
assert "firebase" in format_sync_line(status)
|
|
|
|
def test_pushed_with_peers_reports_both(self) -> None:
|
|
"""Pushed with peers reports both."""
|
|
status = SyncStatus(
|
|
device_id="u",
|
|
backend="firebase",
|
|
pushed=True,
|
|
peer_count=2,
|
|
last_push="2026-08-09 19:05",
|
|
)
|
|
|
|
line = format_sync_line(status)
|
|
|
|
assert "2026-08-09 19:05" in line
|
|
assert "2 peer(s)" in line
|
|
|
|
def test_pushed_with_no_peers_says_no_peers(self) -> None:
|
|
"""Pushed with no peers says no peers."""
|
|
status = SyncStatus(
|
|
device_id="u",
|
|
backend="firebase",
|
|
pushed=True,
|
|
peer_count=0,
|
|
last_push="2026-08-09 19:05",
|
|
)
|
|
|
|
assert "no peers" in format_sync_line(status)
|
|
|
|
|
|
class TestSyncCliFlag:
|
|
"""The --sync flag i3blocks calls."""
|
|
|
|
def test_sync_flag_prints_the_backend_line(
|
|
self, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""--sync must stay on-disk only: safe on every i3blocks tick."""
|
|
with patch(
|
|
"screen_locker.status_view.gather_sync_status",
|
|
return_value=SyncStatus(
|
|
device_id="u",
|
|
backend="firebase",
|
|
pushed=True,
|
|
peer_count=2,
|
|
last_push="2026-08-09 19:05",
|
|
),
|
|
):
|
|
main(["--sync"])
|
|
out = capsys.readouterr().out
|
|
assert "firebase" in out
|
|
assert "2 peer(s)" in out
|