from __future__ import annotations import json from pathlib import Path import app def test_save_and_load_settings_are_isolated_by_profile(tmp_path, monkeypatch) -> None: settings_path = tmp_path / "settings.json" legacy_path = tmp_path / "legacy.json" monkeypatch.setattr(app, "SETTINGS_PATH", settings_path) monkeypatch.setattr(app, "LEGACY_SETTINGS_PATH", legacy_path) app.save_web_settings({"symbol": "AAPL", "period": "1mo"}, profile_id="alice") app.save_web_settings({"symbol": "MSFT", "period": "3mo"}, profile_id="bob") alice = app.load_web_settings(profile_id="alice") bob = app.load_web_settings(profile_id="bob") unknown = app.load_web_settings(profile_id="carol") assert alice["symbol"] == "AAPL" assert alice["period"] == "1mo" assert bob["symbol"] == "MSFT" assert bob["period"] == "3mo" assert unknown["symbol"] == "AAPL" # fallback default def test_load_settings_migrates_legacy_flat_payload_to_default_profile(tmp_path, monkeypatch) -> None: settings_path = tmp_path / "settings.json" legacy_path = tmp_path / "legacy.json" monkeypatch.setattr(app, "SETTINGS_PATH", settings_path) monkeypatch.setattr(app, "LEGACY_SETTINGS_PATH", legacy_path) legacy_payload = {"symbol": "TSLA", "interval": "1h", "max_bars": 9999} legacy_path.write_text(json.dumps(legacy_payload), encoding="utf-8") loaded = app.load_web_settings(profile_id="default") assert loaded["symbol"] == "TSLA" assert loaded["interval"] == "1h" assert loaded["max_bars"] == 5000 # normalized max cap app.save_web_settings({"symbol": "TSLA"}, profile_id="default") stored = json.loads(settings_path.read_text(encoding="utf-8")) assert "profiles" in stored assert "default" in stored["profiles"] assert "settings" in stored["profiles"]["default"] assert "audit" in stored["profiles"]["default"] def test_list_web_profiles_includes_saved_profiles(tmp_path, monkeypatch) -> None: settings_path = tmp_path / "settings.json" legacy_path = tmp_path / "legacy.json" monkeypatch.setattr(app, "SETTINGS_PATH", settings_path) monkeypatch.setattr(app, "LEGACY_SETTINGS_PATH", legacy_path) app.save_web_settings({"symbol": "AAPL"}, profile_id="zoe") app.save_web_settings({"symbol": "AAPL"}, profile_id="alice") profile_ids = app.list_web_profiles() assert "default" in profile_ids assert "alice" in profile_ids assert "zoe" in profile_ids def test_resolve_login_profile_prefers_session_then_query() -> None: assert app.resolve_login_profile("alice", "bob") == "alice" assert app.resolve_login_profile("", "bob") == "bob" assert app.resolve_login_profile(None, None) is None def test_profile_exists_normalizes_input() -> None: profiles = {"default", "alice"} assert app.profile_exists("alice", profiles) is True assert app.profile_exists(" ", profiles) is True # normalizes to default assert app.profile_exists("bob", profiles) is False def test_profile_exists_is_case_insensitive() -> None: profiles = {"default", "Alice"} assert app.profile_exists("alice", profiles) is True assert app.find_existing_profile_id("ALICE", profiles) == "Alice" def test_is_profile_session_expired_after_timeout() -> None: now_epoch = 2000.0 assert app.is_profile_session_expired(1000.0, now_epoch, timeout_sec=900) is True assert app.is_profile_session_expired(1500.0, now_epoch, timeout_sec=900) is False assert app.is_profile_session_expired(None, now_epoch, timeout_sec=900) is False def test_create_profile_with_pin_requires_pin_for_verification(tmp_path, monkeypatch) -> None: settings_path = tmp_path / "settings.json" legacy_path = tmp_path / "legacy.json" monkeypatch.setattr(app, "SETTINGS_PATH", settings_path) monkeypatch.setattr(app, "LEGACY_SETTINGS_PATH", legacy_path) app.create_profile("alice", pin="1234", now_epoch=1700000000) assert app.profile_requires_pin("alice") is True assert app.verify_profile_pin("alice", "1234") is True assert app.verify_profile_pin("alice", "9999") is False def test_audit_stamps_update_on_save_and_login(tmp_path, monkeypatch) -> None: settings_path = tmp_path / "settings.json" legacy_path = tmp_path / "legacy.json" monkeypatch.setattr(app, "SETTINGS_PATH", settings_path) monkeypatch.setattr(app, "LEGACY_SETTINGS_PATH", legacy_path) app.create_profile("alice", now_epoch=1700000000) app.save_web_settings({"symbol": "MSFT"}, profile_id="alice") app.mark_profile_login("alice", now_epoch=1700000010) audit = app.get_profile_audit("alice") assert audit["created_at"] == 1700000000 assert audit["last_login_at"] == 1700000010 assert audit["last_symbol"] == "MSFT" def test_load_settings_migrates_old_profiles_map_structure(tmp_path, monkeypatch) -> None: settings_path = tmp_path / "settings.json" legacy_path = tmp_path / "legacy.json" monkeypatch.setattr(app, "SETTINGS_PATH", settings_path) monkeypatch.setattr(app, "LEGACY_SETTINGS_PATH", legacy_path) old_payload = { "last_profile": "alice", "profiles": { "alice": {"symbol": "TSLA", "interval": "1d"}, "bob": {"symbol": "MSFT", "interval": "1h"}, }, } settings_path.write_text(json.dumps(old_payload), encoding="utf-8") alice_settings = app.load_web_settings("alice") bob_settings = app.load_web_settings("bob") assert alice_settings["symbol"] == "TSLA" assert bob_settings["symbol"] == "MSFT" app.save_web_settings({"symbol": "AAPL"}, profile_id="alice") stored = json.loads(settings_path.read_text(encoding="utf-8")) assert isinstance(stored["profiles"]["alice"], dict) assert "settings" in stored["profiles"]["alice"]