steam-backlog-enforcer/steam_backlog_enforcer/tests
Krzysztof kuhy Rudnicki 63321f16cc
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (push) Waiting to run
Tests / test (push) Waiting to run
Pay an hour of gaming budget for a LeetCode solve
The budget was binary: 8h with a counted workout, 6h without. There was no
base/bonus structure anywhere -- resolve_budget_seconds returned one of two
absolute config values, and the "2h bonus" was emergent arithmetic (8 - 6) that
appeared in no line of code. A second earner could not be bolted onto that
shape without coupling the two.

It is now a sum: 5h floor + 2h workout + 1h LeetCode, so a day is worth 5, 6, 7
or 8 hours. Note the deliberate tightening -- a workout-only day drops 8h to
7h, and a day that earns nothing drops 6h to 5h.

The two earners are read independently and added. That is what "must not
interfere with the workout" means in code: an unreadable LeetCode ledger leaves
a workout day at 7h, not 5h, and there is a test asserting exactly that.

LeetCode is read from leetcode-guard's ledger directly -- it answers even when
leetcode-guard is not running, which is most of the day -- and falls back to its
loopback endpoint. Credits are signature-checked; _leetcode_ledger reproduces
gatelock's canonicalisation in eight lines because gatelock lives in the user's
site-packages and this daemon runs as root, and a pinned vector fails loudly if
that ever drifts.

Failing to get an answer grants nothing AND raises an incident: desktop
notification, warning log, and a line in leetcode_bonus_incidents.log
(gitignored, so it never becomes an uncommitted change), rate-limited to once
per reason per day. A silently missing hour looks exactly like an unearned one.

resolve_budget_seconds is *renamed*, not wrapped: two functions would recreate
the second resolution seam this module's docstring warns about. rules_for calls
it once and carries the breakdown on PlaytimeRules, so /api/budget reports why
today is what it is without resolving again.

Known limitation, accepted: leetcode-guard only writes credits from its lock
window's poll loop, so a voluntary extra solve after the day is settled never
reaches the ledger.

Verified live against real services -- all four cases (5/6/7/8h), the endpoint
fallback, the fail-closed path with its notification, and the rate limit.
1782 tests, 100% branch coverage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fp6zWH9zW1AkGdDyKjrbi8
2026-08-30 15:22:18 +02:00
..
__init__.py test: achieve 100% branch coverage across all python_pkg packages 2026-03-21 17:51:36 +01:00
_fake_proc.py Make serve re-runnable, and cover it to 100% 2026-08-29 18:52:11 +02:00
_fake_session.py Show the gaming budget in the web UI 2026-08-29 07:17:42 +02:00
_fake_xlib.py Bill gaming time only while the user is actually playing 2026-08-28 19:24:14 +02:00
_isolate_playtime.py Show the gaming budget in the web UI 2026-08-29 07:17:42 +02:00
_ledger_fixtures.py Pay an hour of gaming budget for a LeetCode solve 2026-08-30 15:22:18 +02:00
_main_helpers.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
_no_subprocess.py Make serve re-runnable, and cover it to 100% 2026-08-29 18:52:11 +02:00
_no_workout_http.py Pay an hour of gaming budget for a LeetCode solve 2026-08-30 15:22:18 +02:00
_total_block_paths.py Split total-block, playtime and MCP by responsibility 2026-08-23 20:52:08 +02:00
conftest.py Cut the daily gaming budget to 6h on a day with no workout 2026-08-29 23:11:27 +02:00
test_actions_abandon_manual_pick.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_actions_status_payload.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_actions.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_atomic_write.py Show the gaming budget in the web UI 2026-08-29 07:17:42 +02:00
test_attribution_labels.py Show which games make up the gaming budget, and bill osu!lazer 2026-08-29 18:32:03 +02:00
test_bonus_incident.py Pay an hour of gaming budget for a LeetCode solve 2026-08-30 15:22:18 +02:00
test_budget_games.py Show which games make up the gaming budget, and bill osu!lazer 2026-08-29 18:32:03 +02:00
test_budget_log_tail.py Show the gaming budget in the web UI 2026-08-29 07:17:42 +02:00
test_budget_resolve.py Pay an hour of gaming budget for a LeetCode solve 2026-08-30 15:22:18 +02:00
test_budget_view.py Pay an hour of gaming budget for a LeetCode solve 2026-08-30 15:22:18 +02:00
test_cmd_done_part2.py chore: set up as standalone repo 2026-05-28 07:21:29 +02:00
test_cmd_playtime.py Pay an hour of gaming budget for a LeetCode solve 2026-08-30 15:22:18 +02:00
test_cmd_serve.py Make serve re-runnable, and cover it to 100% 2026-08-29 18:52:11 +02:00
test_config_interactive_setup.py Show the gaming budget in the web UI 2026-08-29 07:17:42 +02:00
test_config.py Show which games make up the gaming budget, and bill osu!lazer 2026-08-29 18:32:03 +02:00
test_controller_input.py Bill gaming time only while the user is actually playing 2026-08-28 19:24:14 +02:00
test_counted_procs.py Show which games make up the gaming budget, and bill osu!lazer 2026-08-29 18:32:03 +02:00
test_desktop_env.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_desktop_session_gating.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_desktop_user_cmd.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_do_enforce.py Auto-detect completed manual picks before enforcing the pick cap 2026-08-29 14:53:04 +02:00
test_enforce_loop_part2.py Bill gaming time only while the user is actually playing 2026-08-28 19:24:14 +02:00
test_enforce_loop_part3.py Bill gaming time only while the user is actually playing 2026-08-28 19:24:14 +02:00
test_enforce_loop.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_enforce_steps.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_enforcer_part2.py feat: add block-gaming command (Stage 4) + guard-lib migration cleanup 2026-07-04 11:45:54 +02:00
test_enforcer.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_engagement_probes.py Bill gaming time only while the user is actually playing 2026-08-28 19:24:14 +02:00
test_engagement_types.py Bill gaming time only while the user is actually playing 2026-08-28 19:24:14 +02:00
test_ensure_debug_port.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_ensure_steam_running.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_game_install_guard.py Re-arm the no-library warning before install_game's early return 2026-08-24 14:04:54 +02:00
test_game_install_part2.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_game_install_part3.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_game_install.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_game_names.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_hltb_build_search_payload.py Bring the HLTB modules under the 250-line cap 2026-08-24 15:23:13 +02:00
test_hltb_confidence_helpers.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_hltb_detail_extract_comp100_avg_and_high.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_hltb_detail_fetch_leisure_times_save_interval.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_hltb_detail_fetch_leisure_times.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_hltb_detail_process_game_detail.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_hltb_detail.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_hltb_fetch_hltb_detail_missing.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_hltb_part2.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_hltb_part3.py chore: set up as standalone repo 2026-05-28 07:21:29 +02:00
test_hltb_pick_best_hltb_entry_alias_exact_match_beats_spinoff.py Bring the HLTB modules under the 250-line cap 2026-08-24 15:23:13 +02:00
test_hltb_pick_best_hltb_entry.py Bring the HLTB modules under the 250-line cap 2026-08-24 15:23:13 +02:00
test_hltb_search_extract_leisure_hours.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_hltb_search_fetch_batch_hltb.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_hltb_search_part2.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_hltb_search_zero_comp_100_skipped.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_hltb_search.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_hltb.py Bring the HLTB modules under the 250-line cap 2026-08-24 15:23:13 +02:00
test_leetcode_bonus.py Pay an hour of gaming budget for a LeetCode solve 2026-08-30 15:22:18 +02:00
test_leetcode_ledger_errors.py Pay an hour of gaming budget for a LeetCode solve 2026-08-30 15:22:18 +02:00
test_leetcode_ledger.py Pay an hour of gaming budget for a LeetCode solve 2026-08-30 15:22:18 +02:00
test_library_hider_ops.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_library_hider_part2.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_library_hider.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_main_abandon.py Auto-detect completed manual picks before enforcing the pick cap 2026-08-29 14:53:04 +02:00
test_main_dispatch_extra.py Make serve re-runnable, and cover it to 100% 2026-08-29 18:52:11 +02:00
test_main_dispatch.py Split main.py (1076 lines) into a main/ package 2026-08-23 19:56:34 +02:00
test_main_done.py Split _actions, _whitelist, _cmd_done and hltb under the cap 2026-08-24 14:55:57 +02:00
test_main_entrypoint.py Split main.py (1076 lines) into a main/ package 2026-08-23 19:56:34 +02:00
test_main_install.py Split main.py (1076 lines) into a main/ package 2026-08-23 19:56:34 +02:00
test_main_misc_gating.py Split main.py (1076 lines) into a main/ package 2026-08-23 19:56:34 +02:00
test_main_misc.py Split main.py (1076 lines) into a main/ package 2026-08-23 19:56:34 +02:00
test_main_part2.py Split _actions, _whitelist, _cmd_done and hltb under the cap 2026-08-24 14:55:57 +02:00
test_main_part2b.py Split _actions, _whitelist, _cmd_done and hltb under the cap 2026-08-24 14:55:57 +02:00
test_main_part2c.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_main_pick_cap.py Auto-detect completed manual picks before enforcing the pick cap 2026-08-29 14:53:04 +02:00
test_main_pick_manual.py Split main.py (1076 lines) into a main/ package 2026-08-23 19:56:34 +02:00
test_main_picks.py Split main.py (1076 lines) into a main/ package 2026-08-23 19:56:34 +02:00
test_main_shared.py Split main.py (1076 lines) into a main/ package 2026-08-23 19:56:34 +02:00
test_main_status.py Auto-detect completed manual picks before enforcing the pick cap 2026-08-29 14:53:04 +02:00
test_mcp_actions.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_mcp_gaming_time.py Show the gaming budget in the web UI 2026-08-29 07:17:42 +02:00
test_mcp.py Split total-block, playtime and MCP by responsibility 2026-08-23 20:52:08 +02:00
test_pacman.py Split total-block, playtime and MCP by responsibility 2026-08-23 20:52:08 +02:00
test_pick_completion.py Auto-detect completed manual picks before enforcing the pick cap 2026-08-29 14:53:04 +02:00
test_playtime_attribution.py Show which games make up the gaming budget, and bill osu!lazer 2026-08-29 18:32:03 +02:00
test_playtime_block_part2.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_playtime_block.py Split total-block, playtime and MCP by responsibility 2026-08-23 20:52:08 +02:00
test_playtime_cutoff.py Split total-block, playtime and MCP by responsibility 2026-08-23 20:52:08 +02:00
test_playtime_engagement.py Show which games make up the gaming budget, and bill osu!lazer 2026-08-29 18:32:03 +02:00
test_playtime_history.py Show which games make up the gaming budget, and bill osu!lazer 2026-08-29 18:32:03 +02:00
test_playtime_kill.py Split total-block, playtime and MCP by responsibility 2026-08-23 20:52:08 +02:00
test_playtime_log.py Show the gaming budget in the web UI 2026-08-29 07:17:42 +02:00
test_playtime_part2.py Pay an hour of gaming budget for a LeetCode solve 2026-08-30 15:22:18 +02:00
test_playtime_policy.py Cut the daily gaming budget to 6h on a day with no workout 2026-08-29 23:11:27 +02:00
test_playtime_procs.py Show which games make up the gaming budget, and bill osu!lazer 2026-08-29 18:32:03 +02:00
test_playtime_proctree.py Split total-block, playtime and MCP by responsibility 2026-08-23 20:52:08 +02:00
test_playtime_session.py Show the gaming budget in the web UI 2026-08-29 07:17:42 +02:00
test_playtime_tick.py Pay an hour of gaming budget for a LeetCode solve 2026-08-30 15:22:18 +02:00
test_playtime.py Pay an hour of gaming budget for a LeetCode solve 2026-08-30 15:22:18 +02:00
test_polls_tracking_backfill_polls_for_finished.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_polls_tracking_part2.py Split steam_api and _scanning_confidence under the cap 2026-08-24 14:25:43 +02:00
test_polls_tracking_report_assigned_confidence.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_polls_tracking_report_poll_confidence_chosen_zero_polls_report_poll_confidence_chosen_equals_min.py Split steam_api and _scanning_confidence under the cap 2026-08-24 14:25:43 +02:00
test_polls_tracking_report_poll_confidence_chosen_zero_polls.py Split steam_api and _scanning_confidence under the cap 2026-08-24 14:25:43 +02:00
test_polls_tracking.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_protondb_fetch_batch.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_protondb_fetch_one.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_protondb.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_scanning_cached_confidence_overlay_avoids_refetch.py Bring _stats.py and scanning.py under the 250-line cap 2026-08-24 16:00:54 +02:00
test_scanning_confidence_helpers_pick_next_shortest_candidate_no_echo_whe.py Bring _stats.py and scanning.py under the 250-line cap 2026-08-24 16:00:54 +02:00
test_scanning_confidence_helpers.py Bring _stats.py and scanning.py under the 250-line cap 2026-08-24 16:00:54 +02:00
test_scanning_part2.py Auto-detect completed manual picks before enforcing the pick cap 2026-08-29 14:53:04 +02:00
test_scanning_part3.py Bring _stats.py and scanning.py under the 250-line cap 2026-08-24 16:00:54 +02:00
test_scanning_part4.py Bring _stats.py and scanning.py under the 250-line cap 2026-08-24 16:00:54 +02:00
test_scanning_pick_next_game_sequential.py Bring _stats.py and scanning.py under the 250-line cap 2026-08-24 16:00:54 +02:00
test_scanning_pick_next_game_unknown_hours.py Bring _stats.py and scanning.py under the 250-line cap 2026-08-24 16:00:54 +02:00
test_scanning_pick_next_game.py Auto-detect completed manual picks before enforcing the pick cap 2026-08-29 14:53:04 +02:00
test_scanning.py Bring _stats.py and scanning.py under the 250-line cap 2026-08-24 16:00:54 +02:00
test_screen_hold.py Bill gaming time only while the user is actually playing 2026-08-28 19:24:14 +02:00
test_serve_guard_lifecycle.py Stop the web UI serving numbers the enforcer is not applying 2026-08-29 23:39:40 +02:00
test_serve_guard.py Make the fd-scan test independent of iterdir() order 2026-08-29 18:57:01 +02:00
test_serve_stale.py Stop the web UI serving numbers the enforcer is not applying 2026-08-29 23:39:40 +02:00
test_serve_startup.py Make serve re-runnable, and cover it to 100% 2026-08-29 18:52:11 +02:00
test_stats_cmd_stats_refilter_called_when_ensure_rush_data_re.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_stats_cmd_stats.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_stats_ensure_rush_data.py Bring _stats.py and scanning.py under the 250-line cap 2026-08-24 16:00:54 +02:00
test_stats_print_player_speed_scenario.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_stats_print_worst_example.py Bring _stats.py and scanning.py under the 250-line cap 2026-08-24 16:00:54 +02:00
test_stats_refresh_recently_played_completions.py Bring _stats.py and scanning.py under the 250-line cap 2026-08-24 16:00:54 +02:00
test_stats_sum_hours.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_stats_unplayable_rating_counts_linux_skipped.py Bring _stats.py and scanning.py under the 250-line cap 2026-08-24 16:00:54 +02:00
test_stats.py Bring _stats.py and scanning.py under the 250-line cap 2026-08-24 16:00:54 +02:00
test_steam_api_steam_a_p_i_client.py Split steam_api and _scanning_confidence under the cap 2026-08-24 14:25:43 +02:00
test_steam_api.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_steam_launch_lifecycle.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_steam_restart_guard.py Bill gaming time only while the user is actually playing 2026-08-28 19:24:14 +02:00
test_steam_restart.py Stop the enforcer launching Steam and notifications as root 2026-08-24 13:55:44 +02:00
test_store_blocker_block_via_hosts_install.py Point the iptables tests at the module that now holds the code 2026-08-24 15:07:58 +02:00
test_store_blocker_part2.py Split store_blocker along its one-way call boundaries 2026-08-24 15:06:15 +02:00
test_store_blocker_unblock_store.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_store_blocker.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_total_block_hosts.py Split total-block, playtime and MCP by responsibility 2026-08-23 20:52:08 +02:00
test_total_block_iptables.py Split total-block, playtime and MCP by responsibility 2026-08-23 20:52:08 +02:00
test_total_block_lifecycle.py Split total-block, playtime and MCP by responsibility 2026-08-23 20:52:08 +02:00
test_total_block_purge.py Split total-block, playtime and MCP by responsibility 2026-08-23 20:52:08 +02:00
test_total_block_remnants.py Split total-block, playtime and MCP by responsibility 2026-08-23 20:52:08 +02:00
test_total_block.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_update_restart_guard.py Bill gaming time only while the user is actually playing 2026-08-28 19:24:14 +02:00
test_web_build.py Make serve re-runnable, and cover it to 100% 2026-08-29 18:52:11 +02:00
test_web_dataset_build_games.py Bring _web_dataset.py under the 250-line cap — cap campaign complete 2026-08-24 16:08:12 +02:00
test_web_dataset_compute_pace_vs_h_l_t_b_both_rush_and_leisure_interpolation_comp.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_web_dataset_compute_pace_vs_h_l_t_b.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_web_dataset_count_complete_since_start.py Grade against 3.14 too, not just run on it 2026-08-29 11:44:25 +02:00
test_web_dataset_dataset_to_payload.py Bring _web_dataset.py under the 250-line cap — cap campaign complete 2026-08-24 16:08:12 +02:00
test_web_dataset.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_web_server_stale.py Stop the web UI serving numbers the enforcer is not applying 2026-08-29 23:39:40 +02:00
test_web_server.py Make serve re-runnable, and cover it to 100% 2026-08-29 18:52:11 +02:00
test_whitelist_add_pending_exception.py Bring every test file under the 250-line cap 2026-08-24 14:16:08 +02:00
test_whitelist_immutable_flag_is_append_audit_log.py Split _actions, _whitelist, _cmd_done and hltb under the cap 2026-08-24 14:55:57 +02:00
test_whitelist_immutable_flag_is.py Split _actions, _whitelist, _cmd_done and hltb under the cap 2026-08-24 14:55:57 +02:00
test_whitelist.py Split _actions, _whitelist, _cmd_done and hltb under the cap 2026-08-24 14:55:57 +02:00
test_workout_budget.py Pay an hour of gaming budget for a LeetCode solve 2026-08-30 15:22:18 +02:00
test_x_focus.py Bill gaming time only while the user is actually playing 2026-08-28 19:24:14 +02:00
test_x_probe.py Bill gaming time only while the user is actually playing 2026-08-28 19:24:14 +02:00