"""Tests for state_manager precheck utilities.""" from datetime import timedelta, timezone from coinhunter.services.state_manager import ( clear_run_request_fields, sanitize_state_for_stale_triggers, update_state_after_observation, ) from coinhunter.services.time_utils import utc_iso, utc_now class TestClearRunRequestFields: def test_removes_run_fields(self): state = {"run_requested_at": utc_iso(), "run_request_note": "test"} clear_run_request_fields(state) assert "run_requested_at" not in state assert "run_request_note" not in state class TestSanitizeStateForStaleTriggers: def test_no_changes_when_clean(self): state = {"pending_trigger": False} result = sanitize_state_for_stale_triggers(state) assert result["pending_trigger"] is False assert result["_stale_recovery_notes"] == [] def test_clears_completed_run_request(self): state = { "pending_trigger": True, "run_requested_at": utc_iso(), "last_deep_analysis_at": utc_iso(), } result = sanitize_state_for_stale_triggers(state) assert result["pending_trigger"] is False assert "run_requested_at" not in result assert any("completed run_requested" in note for note in result["_stale_recovery_notes"]) def test_clears_stale_run_request(self): old = (utc_now() - timedelta(minutes=60)).replace(tzinfo=timezone.utc).isoformat() state = { "pending_trigger": False, "run_requested_at": old, } result = sanitize_state_for_stale_triggers(state) assert "run_requested_at" not in result assert any("stale run_requested" in note for note in result["_stale_recovery_notes"]) def test_recovers_stale_pending_trigger(self): old = (utc_now() - timedelta(minutes=60)).replace(tzinfo=timezone.utc).isoformat() state = { "pending_trigger": True, "last_triggered_at": old, } result = sanitize_state_for_stale_triggers(state) assert result["pending_trigger"] is False assert any("stale pending_trigger" in note for note in result["_stale_recovery_notes"]) class TestUpdateStateAfterObservation: def test_updates_last_observed_fields(self): state = {} snapshot = { "generated_at": "2024-01-01T00:00:00Z", "snapshot_hash": "abc", "positions_hash": "pos123", "candidates_hash": "can456", "portfolio_value_usdt": 100.0, "market_regime": "neutral", "positions": [], "top_candidates": [], } analysis = {"should_analyze": False, "details": [], "adaptive_profile": {}} result = update_state_after_observation(state, snapshot, analysis) assert result["last_observed_at"] == snapshot["generated_at"] assert result["last_snapshot_hash"] == "abc" def test_sets_pending_trigger_when_should_analyze(self): state = {} snapshot = { "generated_at": "2024-01-01T00:00:00Z", "snapshot_hash": "abc", "positions_hash": "pos123", "candidates_hash": "can456", "portfolio_value_usdt": 100.0, "market_regime": "neutral", "positions": [], "top_candidates": [], } analysis = { "should_analyze": True, "details": ["price move"], "adaptive_profile": {}, "hard_reasons": ["moon"], "signal_delta": 1.5, } result = update_state_after_observation(state, snapshot, analysis) assert result["pending_trigger"] is True assert result["pending_reasons"] == ["price move"]