utils/crdt-sync/crdt_sync/tests/test_tool_desktop_client.py
Krzysztof kuhy Rudnicki bce59127eb
Some checks are pending
crdt-sync pre-commit / pre-commit (push) Waiting to run
crdt-sync tests / test (push) Waiting to run
dependency freshness / dependencies on newest stable (push) Waiting to run
file length / file-length (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
palette drift / palette (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
Add Desktop OAuth client register + verify
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 22:11:44 +01:00

187 lines
5.7 KiB
Python

# Copyright (c) 2026 Krzysztof Rudnicki
"""Tests for ``tool/desktop_client``: picking up and validating the client JSON.
The Android client JSON Google offers for download has the same top-level
``installed`` key and project as a Desktop one, so the negative fixture here is
that exact shape: it must be refused, or the wrong client gets stored.
"""
from __future__ import annotations
import json
import os
import stat
from typing import TYPE_CHECKING
import pytest
from tool import desktop_client as dc
if TYPE_CHECKING:
from pathlib import Path
_CLIENT_ID = "1-abc.apps.googleusercontent.com"
_HELD = "the-desktop-client-credential"
def _installed(**overrides: object) -> dict[str, object]:
body: dict[str, object] = {
"client_id": _CLIENT_ID,
"project_id": "kuhy-syncs",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"client_secret": _HELD,
"redirect_uris": ["http://localhost"],
}
body.update(overrides)
return body
def _text(**overrides: object) -> str:
return json.dumps({"installed": _installed(**overrides)})
def _android_text() -> str:
body = _installed()
del body["client_secret"]
del body["redirect_uris"]
return json.dumps({"installed": body})
def test_valid_desktop_json_parses() -> None:
client = dc.parse_client_json(_text())
assert client == dc.DesktopClient(_CLIENT_ID, _HELD, "kuhy-syncs")
@pytest.mark.parametrize(
("text", "needle"),
[
("{not json", "not valid JSON"),
("[]", "JSON object"),
(json.dumps({"web": _installed()}), "'installed'"),
(_text(project_id="other"), "other"),
(_text(client_id="plain"), "client_id"),
(_android_text(), "client_secret"),
(_text(redirect_uris=["https://x.example"]), "http://localhost"),
(json.dumps({"installed": "nope"}), "'installed'"),
],
)
def test_invalid_json_is_refused_with_a_reason(text: str, needle: str) -> None:
with pytest.raises(dc.ClientFileError, match=needle):
dc.parse_client_json(text)
def _touch(directory: Path, name: str, text: str, mtime: float) -> Path:
path = directory / name
path.write_text(text, encoding="utf-8")
os.utime(path, (mtime, mtime))
return path
def _wait(directory: Path, since: float, skipped: list[str]) -> dc.Pickup | None:
return dc.wait_for_client_json(
directory, since, attempts=3, pause=lambda: None, report=skipped.append
)
def test_pickup_ignores_old_files_and_the_android_json(tmp_path: Path) -> None:
_touch(tmp_path, "client_secret_old.json", _text(), 50.0)
_touch(tmp_path, "client_secret_android.json", _android_text(), 150.0)
_touch(tmp_path, "notes.json", _text(), 150.0)
skipped: list[str] = []
assert _wait(tmp_path, 100.0, skipped) is None
assert len(skipped) == 1
assert "client_secret_android.json" in skipped[0]
assert "client_secret" in skipped[0]
def test_pickup_takes_the_newest_valid_file(tmp_path: Path) -> None:
_touch(
tmp_path,
"client_secret_a.json",
_text(client_id="1-a.apps.googleusercontent.com"),
120.0,
)
newest = _touch(tmp_path, "client_secret_b.json", _text(), 130.0)
found = _wait(tmp_path, 100.0, [])
assert found is not None
assert found.path == newest
assert found.client.client_id == _CLIENT_ID
def test_pickup_waits_for_the_file_to_appear(tmp_path: Path) -> None:
def pause() -> None:
_touch(tmp_path, "client_secret_late.json", _text(), 500.0)
found = dc.wait_for_client_json(tmp_path, 100.0, attempts=3, pause=pause)
assert found is not None
def test_pickup_pauses_between_attempts_only(tmp_path: Path) -> None:
pauses: list[int] = []
result = dc.wait_for_client_json(
tmp_path, 0.0, attempts=3, pause=lambda: pauses.append(1)
)
assert result is None
assert len(pauses) == 2
def test_store_client_is_private_and_consumes_the_download(tmp_path: Path) -> None:
source = _touch(tmp_path, "client_secret_x.json", _text(), 1.0)
dest = tmp_path / "conf" / "desktop_client.json"
dc.store_client(source, dest)
assert stat.S_IMODE(dest.stat().st_mode) == 0o600
assert dc.load_client(dest).client_id == _CLIENT_ID
assert not source.exists()
def test_store_client_overwrites_and_tightens_an_existing_file(tmp_path: Path) -> None:
dest = tmp_path / "desktop_client.json"
dest.write_text("old", encoding="utf-8")
dest.chmod(0o644)
dc.store_client(_touch(tmp_path, "client_secret_y.json", _text(), 1.0), dest)
assert stat.S_IMODE(dest.stat().st_mode) == 0o600
assert dest.read_text(encoding="utf-8") == _text()
def test_load_client_points_at_the_register_script_when_missing(tmp_path: Path) -> None:
with pytest.raises(dc.ClientFileError, match=r"register_desktop_client\.sh"):
dc.load_client(tmp_path / "absent.json")
def test_main_pickup_stores_the_client(tmp_path: Path) -> None:
_touch(tmp_path, "client_secret_z.json", _text(), 200.0)
dest = tmp_path / "out" / "desktop_client.json"
code = dc.main(
[
"pickup",
"--since",
"100",
"--timeout",
"5",
"--downloads",
str(tmp_path),
"--dest",
str(dest),
]
)
assert code == 0
assert dc.load_client(dest).project_id == "kuhy-syncs"
def test_main_pickup_times_out(tmp_path: Path) -> None:
code = dc.main(
[
"pickup",
"--since",
"100",
"--timeout",
"0",
"--downloads",
str(tmp_path),
"--dest",
str(tmp_path / "d.json"),
]
)
assert code == 1