steam-backlog-enforcer/steam_backlog_enforcer/tests
Krzysztof kuhy Rudnicki a191c8e459 Release a game after one new achievement instead of 100% completion
Requiring 100% locked the user onto one game for weeks, which proved
ineffective. The assignment and every manual pick now release as soon as one
achievement is unlocked after the game was assigned (Steam unlock_time vs the
pick's started_at / State.current_assigned_at).

- finished_app_ids still means 100% complete; a game released below 100% gets
  released_at plus a 7-day cooldown and returns to the pool.
- Next pick: least recently assigned first, then shortest HLTB time.
- Releasing never evicts: released/expired picks stay installed and playable
  until the user explicitly chooses another game (new pick-manual, or
  accepting the next assignment); with no replacement the current game stays.
- Tampering detection judges a released game by unlocks after its release.
- The daemon reloads every State field each tick so its sweep save cannot
  overwrite cooldowns or assignment times written by the CLI.
- State.load backfills assignment times once and persists them; load-time
  migrations moved to _state_migrations.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhzaTAWtRfJk8gre6NLSka
2026-09-26 16:46:03 +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 Remove engagement gating from the gaming budget 2026-09-02 18:14:27 +02:00
_fake_xlib.py Bill gaming time only while the user is actually playing 2026-08-28 19:24:14 +02:00
_isolate_incidents.py Stop the suite writing into the real incident log 2026-08-30 15:30:22 +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 Release a game after one new achievement instead of 100% completion 2026-09-26 16:46:03 +02:00
_no_fast_install.py Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +02:00
_no_subprocess.py Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +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 Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +02:00
test_actions_abandon_manual_pick.py Import split-out names from the modules that own them; drop the four lazy facades 2026-09-12 20:22:44 +02:00
test_actions_status_payload.py Import split-out names from the modules that own them; drop the four lazy facades 2026-09-12 20:22:44 +02:00
test_actions.py Import split-out names from the modules that own them; drop the four lazy facades 2026-09-12 20:22:44 +02:00
test_assignment_progress.py Release a game after one new achievement instead of 100% completion 2026-09-26 16:46:03 +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 Remove engagement gating from the gaming budget 2026-09-02 18:14:27 +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 Remove engagement gating from the gaming budget 2026-09-02 18:14:27 +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_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 Pass SDL_JOYSTICK_HIDAPI_LG4FF=0 to Steam so the G29 keeps force feedback 2026-09-14 21:35:15 +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 Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +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_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_fast_install_guards.py Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +02:00
test_fast_install_helpers.py Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +02:00
test_fast_install_runs.py Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +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 Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +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 pylint: fold the remaining split-era duplicates back into single owners 2026-09-12 20:49:12 +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 pylint: fold the remaining split-era duplicates back into single owners 2026-09-12 20:49:12 +02:00
test_hltb_part2.py pylint: fold the remaining split-era duplicates back into single owners 2026-09-12 20:49:12 +02:00
test_hltb_part3.py Import split-out names from the modules that own them; drop the four lazy facades 2026-09-12 20:22:44 +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 pylint: clear the mechanical residue, the howlongtobeatpy 1.0.23 call, and three duplicate blocks 2026-09-12 20:31:27 +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 pylint: fold the remaining split-era duplicates back into single owners 2026-09-12 20:49:12 +02:00
test_hltb.py pylint: fold the remaining split-era duplicates back into single owners 2026-09-12 20:49:12 +02:00
test_leetcode_bonus.py pylint: fold the remaining split-era duplicates back into single owners 2026-09-12 20:49:12 +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 Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +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 Release a game after one new achievement instead of 100% completion 2026-09-26 16:46:03 +02:00
test_main_part2b.py Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +02:00
test_main_part2c.py Release a game after one new achievement instead of 100% completion 2026-09-26 16:46:03 +02:00
test_main_pick_cap.py Release a game after one new achievement instead of 100% completion 2026-09-26 16:46:03 +02:00
test_main_pick_manual_cascade.py Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +02:00
test_main_pick_manual.py Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +02:00
test_main_picks.py Deduplicate snapshot loading, pick application and library hiding into shared helpers 2026-09-25 18:44:07 +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 Deduplicate snapshot loading, pick application and library hiding into shared helpers 2026-09-25 18:44:07 +02:00
test_mcp_actions.py Deduplicate snapshot loading, pick application and library hiding into shared helpers 2026-09-25 18:44:07 +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 Deduplicate snapshot loading, pick application and library hiding into shared helpers 2026-09-25 18:44:07 +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 Release a game after one new achievement instead of 100% completion 2026-09-26 16:46:03 +02:00
test_playtime_attribution.py Remove engagement gating from the gaming budget 2026-09-02 18:14:27 +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_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 Remove engagement gating from the gaming budget 2026-09-02 18:14:27 +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 Remove engagement gating from the gaming budget 2026-09-02 18:14:27 +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 pylint: fold the remaining split-era duplicates back into single owners 2026-09-12 20:49:12 +02:00
test_polls_tracking_part2.py pylint: fold the remaining split-era duplicates back into single owners 2026-09-12 20:49:12 +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_release_lifecycle.py Release a game after one new achievement instead of 100% completion 2026-09-26 16:46:03 +02:00
test_scanning_cached_confidence_overlay_avoids_refetch.py Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +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 Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +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 Release a game after one new achievement instead of 100% completion 2026-09-26 16:46:03 +02:00
test_scanning_pick_next_game_unknown_hours.py Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +02:00
test_scanning_pick_next_game.py Install games with steam-game-installer (depotdownloader) for speed, falling back to steam://install 2026-09-25 18:45:27 +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_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 Deduplicate snapshot loading, pick application and library hiding into shared helpers 2026-09-25 18:44:07 +02:00
test_stats_cmd_stats.py Deduplicate snapshot loading, pick application and library hiding into shared helpers 2026-09-25 18:44:07 +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 pylint: fold the remaining split-era duplicates back into single owners 2026-09-12 20:49:12 +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 Import split-out names from the modules that own them; drop the four lazy facades 2026-09-12 20:22:44 +02:00
test_whitelist_immutable_flag_is_append_audit_log.py Import split-out names from the modules that own them; drop the four lazy facades 2026-09-12 20:22:44 +02:00
test_whitelist_immutable_flag_is.py Import split-out names from the modules that own them; drop the four lazy facades 2026-09-12 20:22:44 +02:00
test_whitelist.py Import split-out names from the modules that own them; drop the four lazy facades 2026-09-12 20:22:44 +02:00
test_workout_budget.py pylint: fold the remaining split-era duplicates back into single owners 2026-09-12 20:49:12 +02:00