| 1 | """Tests for browser cookie extraction integration in env.py.""" |
| 2 | |
| 3 | import os |
| 4 | from unittest.mock import patch |
| 5 | |
| 6 | import pytest |
| 7 | |
| 8 | from lib.env import ConfigLoadPolicy, extract_browser_credentials, COOKIE_DOMAINS |
| 9 | |
| 10 | |
| 11 | def _base_config(**overrides): |
| 12 | """Return a minimal config dict with common defaults.""" |
| 13 | cfg = { |
| 14 | "AUTH_TOKEN": None, |
| 15 | "CT0": None, |
| 16 | "TRUTHSOCIAL_TOKEN": None, |
| 17 | "FROM_BROWSER": None, |
| 18 | "SETUP_COMPLETE": None, |
| 19 | } |
| 20 | cfg.update(overrides) |
| 21 | return cfg |
| 22 | |
| 23 | |
| 24 | class TestExtractBrowserCredentials: |
| 25 | """Unit tests for extract_browser_credentials().""" |
| 26 | |
| 27 | @patch("lib.cookie_extract.extract_cookies") |
| 28 | def test_auto_populates_credentials(self, mock_extract): |
| 29 | mock_extract.return_value = {"auth_token": "tok123", "ct0": "ct0val"} |
| 30 | config = _base_config(FROM_BROWSER="auto") |
| 31 | result = extract_browser_credentials(config) |
| 32 | assert result["AUTH_TOKEN"] == "tok123" |
| 33 | assert result["CT0"] == "ct0val" |
| 34 | # auto mode tries firefox first, then safari, then chrome |
| 35 | mock_extract.assert_any_call("firefox", ".x.com", ["auth_token", "ct0"]) |
| 36 | |
| 37 | @patch("lib.cookie_extract.extract_cookies") |
| 38 | def test_explicit_auth_token_skips_x_extraction(self, mock_extract): |
| 39 | mock_extract.return_value = None |
| 40 | config = _base_config( |
| 41 | AUTH_TOKEN="explicit_token", CT0="explicit_ct0", |
| 42 | FROM_BROWSER="auto", |
| 43 | ) |
| 44 | result = extract_browser_credentials(config) |
| 45 | assert "AUTH_TOKEN" not in result |
| 46 | assert "CT0" not in result |
| 47 | for call in mock_extract.call_args_list: |
| 48 | assert call[0][1] != ".x.com" |
| 49 | |
| 50 | @patch("lib.cookie_extract.extract_cookies") |
| 51 | def test_from_browser_off_skips_all(self, mock_extract): |
| 52 | config = _base_config(FROM_BROWSER="off") |
| 53 | result = extract_browser_credentials(config) |
| 54 | assert result == {} |
| 55 | mock_extract.assert_not_called() |
| 56 | |
| 57 | @patch("lib.cookie_extract.extract_cookies") |
| 58 | def test_no_from_browser_skips_all(self, mock_extract): |
| 59 | """Default (no FROM_BROWSER): reads no browser cookies.""" |
| 60 | mock_extract.return_value = None |
| 61 | config = _base_config() |
| 62 | result = extract_browser_credentials(config) |
| 63 | assert result == {} |
| 64 | mock_extract.assert_not_called() |
| 65 | |
| 66 | @patch("lib.cookie_extract.extract_cookies") |
| 67 | def test_from_browser_firefox_only(self, mock_extract): |
| 68 | mock_extract.return_value = {"auth_token": "ff_tok", "ct0": "ff_ct0"} |
| 69 | config = _base_config(FROM_BROWSER="firefox") |
| 70 | result = extract_browser_credentials(config) |
| 71 | assert result["AUTH_TOKEN"] == "ff_tok" |
| 72 | for call in mock_extract.call_args_list: |
| 73 | assert call[0][0] == "firefox" |
| 74 | |
| 75 | @patch("lib.cookie_extract.extract_cookies") |
| 76 | def test_extraction_returns_none_config_unchanged(self, mock_extract): |
| 77 | mock_extract.return_value = None |
| 78 | config = _base_config(FROM_BROWSER="auto") |
| 79 | result = extract_browser_credentials(config) |
| 80 | assert "AUTH_TOKEN" not in result |
| 81 | assert "CT0" not in result |
| 82 | |
| 83 | @patch("lib.cookie_extract.extract_cookies") |
| 84 | def test_extraction_raises_exception_caught(self, mock_extract): |
| 85 | mock_extract.side_effect = RuntimeError("database locked") |
| 86 | config = _base_config(FROM_BROWSER="auto") |
| 87 | result = extract_browser_credentials(config) |
| 88 | assert "AUTH_TOKEN" not in result |
| 89 | assert "CT0" not in result |
| 90 | |
| 91 | @patch("lib.cookie_extract.extract_cookies") |
| 92 | def test_partial_credentials_only_fills_missing(self, mock_extract): |
| 93 | mock_extract.return_value = {"auth_token": "cookie_tok", "ct0": "cookie_ct0"} |
| 94 | config = _base_config( |
| 95 | AUTH_TOKEN="explicit", CT0=None, |
| 96 | FROM_BROWSER="auto", |
| 97 | ) |
| 98 | result = extract_browser_credentials(config) |
| 99 | assert "AUTH_TOKEN" not in result |
| 100 | assert result["CT0"] == "cookie_ct0" |
| 101 | |
| 102 | |
| 103 | class TestGetConfigCookieIntegration: |
| 104 | """Integration tests for policy-gated cookie extraction in get_config().""" |
| 105 | |
| 106 | @patch("lib.cookie_extract.extract_cookies") |
| 107 | @patch("lib.env._find_project_env", return_value=None) |
| 108 | @patch("lib.env.load_env_file", return_value={}) |
| 109 | @patch("lib.env._load_keychain", return_value={}) |
| 110 | @patch("lib.env.get_openai_auth") |
| 111 | def test_get_config_default_does_not_extract_cookies( |
| 112 | self, mock_openai, mock_keychain, mock_load, mock_proj, mock_extract |
| 113 | ): |
| 114 | from lib.env import get_config, OpenAIAuth |
| 115 | mock_openai.return_value = OpenAIAuth( |
| 116 | token=None, source="none", status="missing", |
| 117 | ) |
| 118 | mock_extract.return_value = {"auth_token": "browser_tok", "ct0": "browser_ct0"} |
| 119 | env_patch = { |
| 120 | "SETUP_COMPLETE": "true", |
| 121 | "FROM_BROWSER": "auto", |
| 122 | "LAST30DAYS_CONFIG_DIR": "", |
| 123 | } |
| 124 | with patch.dict(os.environ, env_patch, clear=False): |
| 125 | config = get_config() |
| 126 | assert config["AUTH_TOKEN"] is None |
| 127 | assert config["CT0"] is None |
| 128 | mock_extract.assert_not_called() |
| 129 | |
| 130 | @patch("lib.cookie_extract.extract_cookies") |
| 131 | @patch("lib.env._find_project_env", return_value=None) |
| 132 | @patch("lib.env.load_env_file", return_value={}) |
| 133 | @patch("lib.env._load_keychain", return_value={}) |
| 134 | @patch("lib.env.get_openai_auth") |
| 135 | def test_get_config_with_cookie_policy_injects_cookies( |
| 136 | self, mock_openai, mock_keychain, mock_load, mock_proj, mock_extract |
| 137 | ): |
| 138 | from lib.env import get_config, OpenAIAuth |
| 139 | mock_openai.return_value = OpenAIAuth( |
| 140 | token=None, source="none", status="missing", |
| 141 | ) |
| 142 | mock_extract.return_value = {"auth_token": "browser_tok", "ct0": "browser_ct0"} |
| 143 | env_patch = { |
| 144 | "SETUP_COMPLETE": "true", |
| 145 | "FROM_BROWSER": "auto", |
| 146 | "LAST30DAYS_CONFIG_DIR": "", |
| 147 | } |
| 148 | with patch.dict(os.environ, env_patch, clear=False): |
| 149 | config = get_config(policy=ConfigLoadPolicy(browser_cookies="read")) |
| 150 | assert config["AUTH_TOKEN"] == "browser_tok" |
| 151 | assert config["CT0"] == "browser_ct0" |
| 152 |