163 lines
6.6 KiB
Python
163 lines
6.6 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from web_core.auth import profile_store as store
|
|
|
|
|
|
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(store, "SETTINGS_PATH", settings_path)
|
|
monkeypatch.setattr(store, "LEGACY_SETTINGS_PATH", legacy_path)
|
|
|
|
store.save_web_settings({"symbol": "AAPL", "period": "1mo"}, profile_id="alice")
|
|
store.save_web_settings({"symbol": "MSFT", "period": "3mo"}, profile_id="bob")
|
|
|
|
alice = store.load_web_settings(profile_id="alice")
|
|
bob = store.load_web_settings(profile_id="bob")
|
|
unknown = store.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(store, "SETTINGS_PATH", settings_path)
|
|
monkeypatch.setattr(store, "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 = store.load_web_settings(profile_id="default")
|
|
assert loaded["symbol"] == "TSLA"
|
|
assert loaded["interval"] == "1h"
|
|
assert loaded["max_bars"] == 5000 # normalized max cap
|
|
|
|
store.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(store, "SETTINGS_PATH", settings_path)
|
|
monkeypatch.setattr(store, "LEGACY_SETTINGS_PATH", legacy_path)
|
|
|
|
store.save_web_settings({"symbol": "AAPL"}, profile_id="zoe")
|
|
store.save_web_settings({"symbol": "AAPL"}, profile_id="alice")
|
|
|
|
profile_ids = store.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 store.resolve_login_profile("alice", "bob") == "alice"
|
|
assert store.resolve_login_profile("", "bob") == "bob"
|
|
assert store.resolve_login_profile(None, None) is None
|
|
|
|
|
|
def test_profile_exists_normalizes_input() -> None:
|
|
profiles = {"default", "alice"}
|
|
assert store.profile_exists("alice", profiles) is True
|
|
assert store.profile_exists(" ", profiles) is True # normalizes to default
|
|
assert store.profile_exists("bob", profiles) is False
|
|
|
|
|
|
def test_profile_exists_is_case_insensitive() -> None:
|
|
profiles = {"default", "Alice"}
|
|
assert store.profile_exists("alice", profiles) is True
|
|
assert store.find_existing_profile_id("ALICE", profiles) == "Alice"
|
|
|
|
|
|
def test_is_profile_session_expired_after_timeout() -> None:
|
|
now_epoch = 2000.0
|
|
assert store.is_profile_session_expired(1000.0, now_epoch, timeout_sec=900) is True
|
|
assert store.is_profile_session_expired(1500.0, now_epoch, timeout_sec=900) is False
|
|
assert store.is_profile_session_expired(None, now_epoch, timeout_sec=900) is False
|
|
|
|
|
|
def test_is_truthy_flag_parses_common_values() -> None:
|
|
assert store.is_truthy_flag("1") is True
|
|
assert store.is_truthy_flag("true") is True
|
|
assert store.is_truthy_flag("yes") is True
|
|
assert store.is_truthy_flag("0") is False
|
|
assert store.is_truthy_flag(None) 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(store, "SETTINGS_PATH", settings_path)
|
|
monkeypatch.setattr(store, "LEGACY_SETTINGS_PATH", legacy_path)
|
|
|
|
store.create_profile("alice", pin="1234", now_epoch=1700000000)
|
|
|
|
assert store.profile_requires_pin("alice") is True
|
|
assert store.verify_profile_pin("alice", "1234") is True
|
|
assert store.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(store, "SETTINGS_PATH", settings_path)
|
|
monkeypatch.setattr(store, "LEGACY_SETTINGS_PATH", legacy_path)
|
|
|
|
store.create_profile("alice", now_epoch=1700000000)
|
|
store.save_web_settings({"symbol": "MSFT"}, profile_id="alice")
|
|
store.mark_profile_login("alice", now_epoch=1700000010)
|
|
|
|
audit = store.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(store, "SETTINGS_PATH", settings_path)
|
|
monkeypatch.setattr(store, "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 = store.load_web_settings("alice")
|
|
bob_settings = store.load_web_settings("bob")
|
|
assert alice_settings["symbol"] == "TSLA"
|
|
assert bob_settings["symbol"] == "MSFT"
|
|
|
|
store.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"]
|
|
|
|
|
|
def test_watchlist_round_trip_keeps_multiple_symbols(tmp_path, monkeypatch) -> None:
|
|
settings_path = tmp_path / "settings.json"
|
|
legacy_path = tmp_path / "legacy.json"
|
|
monkeypatch.setattr(store, "SETTINGS_PATH", settings_path)
|
|
monkeypatch.setattr(store, "LEGACY_SETTINGS_PATH", legacy_path)
|
|
|
|
store.save_web_settings({"symbol": "AMD", "watchlist": ["AMD", "TSLA"]}, profile_id="matt")
|
|
loaded = store.load_web_settings(profile_id="matt")
|
|
assert loaded["watchlist"] == ["AMD", "TSLA"]
|