steam-backlog-enforcer/steam_backlog_enforcer/tests/test_leetcode_ledger.py
Krzysztof kuhy Rudnicki d2565416aa Pylint gate covers tests, no suppressions
Tests are now linted by the 10.00 gate (ignore-paths dropped), with no
per-test disables. CtlServer/DaemonControl members the tests drive are
public (renamed, behaviour unchanged); SteamAPIClient._get takes its
params explicitly, as every caller already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 12:17:07 +01:00

223 lines
7.4 KiB
Python

"""Tests for _leetcode_ledger: which entries count as a solve today.
The signature check is load-bearing. The shared reader in ``earned_time``
reproduces gatelock's canonicalisation rather than importing it (gatelock pulls
in tkinter, and the enforcer runs headless as root).
``TestSignatureCanonicalisation`` pins it to a fixed vector so the duplication
cannot drift silently -- if leetcode-guard ever changes how it signs, that test
fails instead of every credit quietly ceasing to count.
Every read here passes ``gate_off=True``: these pin how a credit row is
parsed, which only decides on a day the gate stands down. On any other day
only the paid-in-full charge earns (``test_leetcode_paid_day.py``).
"""
from __future__ import annotations
from datetime import datetime, timedelta
from typing import TYPE_CHECKING
import earned_time
from steam_backlog_enforcer._leetcode_ledger import read_ledger_solved_today
from steam_backlog_enforcer.tests._ledger_fixtures import (
KEY,
credit,
fixture_key_file,
sign,
write_ledger,
)
if TYPE_CHECKING:
from pathlib import Path
import pytest
__all__ = ["fixture_key_file"]
class TestSignatureCanonicalisation:
"""The shared canonicalisation must keep matching gatelock's."""
def test_a_known_vector_still_verifies(self) -> None:
"""Pinned so a change in how entries are signed fails loudly here."""
entry = {
"entry_id": "ac:1",
"kind": "credit",
"day": "2026-08-30",
"created_at": "2026-08-30T14:00:00+02:00",
"amount": 1,
"device": "pc",
"detail": {
"title_slug": "two-sum",
"lang": "python3",
"source": "leetcode",
"submitted_at": "1788090000",
},
"hmac": "4f6986bdb019bf28f607918ffb1b5bc79d252f887ade2e25aa83a31d1e49423e",
}
assert earned_time.entry_signature(entry, KEY) == entry["hmac"]
def test_a_tampered_entry_fails(self, tmp_path: Path, key_file: Path) -> None:
"""Changing any field must invalidate the signature.
Args:
tmp_path: pytest's temporary directory.
key_file: The patched signing key.
"""
assert key_file.exists()
entry = credit(when=datetime.now().astimezone())
entry["amount"] = 99
assert (
read_ledger_solved_today(write_ledger(tmp_path, [entry]), gate_off=True)
is False
)
def test_a_missing_signature_fails(self, tmp_path: Path, key_file: Path) -> None:
"""An entry with no hmac field is not a signed entry.
Args:
tmp_path: pytest's temporary directory.
key_file: The patched signing key.
"""
assert key_file.exists()
entry = credit(when=datetime.now().astimezone())
del entry["hmac"]
assert (
read_ledger_solved_today(write_ledger(tmp_path, [entry]), gate_off=True)
is False
)
class TestReadingTheLedger:
"""Which entries count, and which are ignored."""
def test_a_credit_today_counts(self, tmp_path: Path, key_file: Path) -> None:
"""The ordinary case.
Args:
tmp_path: pytest's temporary directory.
key_file: The patched signing key.
"""
assert key_file.exists()
ledger = write_ledger(tmp_path, [credit(when=datetime.now().astimezone())])
assert read_ledger_solved_today(ledger, gate_off=True) is True
def test_a_credit_yesterday_does_not(self, tmp_path: Path, key_file: Path) -> None:
"""Yesterday's solve bought yesterday's hour, not today's.
Args:
tmp_path: pytest's temporary directory.
key_file: The patched signing key.
"""
assert key_file.exists()
yesterday = datetime.now().astimezone() - timedelta(days=1)
ledger = write_ledger(tmp_path, [credit(when=yesterday)])
assert read_ledger_solved_today(ledger, gate_off=True) is False
def test_charges_and_seen_entries_do_not_count(
self, tmp_path: Path, key_file: Path
) -> None:
"""A settled day is not a solved day, and seeding is worth nothing.
Args:
tmp_path: pytest's temporary directory.
key_file: The patched signing key.
"""
assert key_file.exists()
now = datetime.now().astimezone()
today = now.date().isoformat()
rows = [
sign(
{
"entry_id": f"charge:{today}",
"kind": "charge",
"day": today,
"created_at": now.isoformat(),
"amount": 2,
"device": "pc",
"detail": {"source": "escape"},
}
),
sign(
{
"entry_id": "ac:9",
"kind": "seen",
"day": today,
"created_at": now.isoformat(),
"amount": 0,
"device": "pc",
"detail": {"submitted_at": str(int(now.timestamp()))},
}
),
]
assert (
read_ledger_solved_today(write_ledger(tmp_path, rows), gate_off=True)
is False
)
def test_a_forged_credit_does_not_count(
self, tmp_path: Path, key_file: Path
) -> None:
"""Appending JSON must not buy an hour.
Args:
tmp_path: pytest's temporary directory.
key_file: The patched signing key.
"""
assert key_file.exists()
forged = credit(when=datetime.now().astimezone())
forged["hmac"] = "00" * 32
assert (
read_ledger_solved_today(write_ledger(tmp_path, [forged]), gate_off=True)
is False
)
def test_a_missing_submitted_at_falls_back_to_the_day_key(
self, tmp_path: Path, key_file: Path
) -> None:
"""Older credits predate the field and must still count.
Args:
tmp_path: pytest's temporary directory.
key_file: The patched signing key.
"""
assert key_file.exists()
now = datetime.now().astimezone()
entry = sign(
{
"entry_id": "ac:2",
"kind": "credit",
"day": now.date().isoformat(),
"created_at": now.isoformat(),
"amount": 1,
"device": "pc",
"detail": {"source": "leetcode"},
}
)
assert (
read_ledger_solved_today(write_ledger(tmp_path, [entry]), gate_off=True)
is True
)
def test_an_unparsable_submitted_at_is_reported(
self, tmp_path: Path, key_file: Path, caplog: pytest.LogCaptureFixture
) -> None:
"""Corrupt data falls back rather than crashing, and says so.
Args:
tmp_path: pytest's temporary directory.
key_file: The patched signing key.
caplog: pytest's log capture.
"""
assert key_file.exists()
now = datetime.now().astimezone()
entry = credit(when=now, entry_id="ac:3")
entry["detail"]["submitted_at"] = "not-a-number"
entry = sign({k: v for k, v in entry.items() if k != "hmac"})
assert (
read_ledger_solved_today(write_ledger(tmp_path, [entry]), gate_off=True)
is True
)
assert "unparsable submitted_at" in caplog.text