Files
AIChatExporter/tests/test_browser_tokens.py
T

156 lines
5.5 KiB
Python

"""Unit tests for browser cookie extraction (browser_cookie3 mocked)."""
import sys
import types
from types import SimpleNamespace
import pytest
from src.browser_tokens import (
BrowserTokenError,
extract_chatgpt_tokens,
extract_claude_session,
)
def _fake_bc3(monkeypatch, cookies_by_domain, raises=None):
"""Install a stub browser_cookie3 module whose ``brave`` loader returns
SimpleNamespace cookies for the requested domain (or raises)."""
module = types.ModuleType("browser_cookie3")
def brave(domain_name=""):
if raises is not None:
raise raises
return [
SimpleNamespace(name=name, value=value)
for name, value in cookies_by_domain.get(domain_name, {}).items()
]
module.brave = brave
monkeypatch.setitem(sys.modules, "browser_cookie3", module)
return module
class TestExtractChatGPT:
def test_chunked_cookies(self, monkeypatch):
_fake_bc3(monkeypatch, {
"chatgpt.com": {
"__Secure-next-auth.session-token.0": "eyJpart0",
"__Secure-next-auth.session-token.1": "part1rest",
},
})
assert extract_chatgpt_tokens("brave") == ("eyJpart0", "part1rest")
def test_single_cookie_fallback(self, monkeypatch):
_fake_bc3(monkeypatch, {
"chatgpt.com": {"__Secure-next-auth.session-token": "eyJsingle"},
})
assert extract_chatgpt_tokens("brave") == ("eyJsingle", None)
def test_missing_cookie_raises_with_login_hint(self, monkeypatch):
_fake_bc3(monkeypatch, {"chatgpt.com": {"unrelated": "x"}})
with pytest.raises(BrowserTokenError, match="log in"):
extract_chatgpt_tokens("brave")
def test_loader_failure_wrapped(self, monkeypatch):
_fake_bc3(monkeypatch, {}, raises=RuntimeError("database is locked"))
with pytest.raises(BrowserTokenError, match="Close the browser"):
extract_chatgpt_tokens("brave")
def test_unsupported_browser_rejected(self):
with pytest.raises(BrowserTokenError, match="Unsupported browser"):
extract_chatgpt_tokens("netscape")
class TestExtractClaude:
def test_session_key(self, monkeypatch):
_fake_bc3(monkeypatch, {"claude.ai": {"sessionKey": "sk-ant-sid01-xyz"}})
assert extract_claude_session("brave") == "sk-ant-sid01-xyz"
def test_missing_raises(self, monkeypatch):
_fake_bc3(monkeypatch, {"claude.ai": {}})
with pytest.raises(BrowserTokenError, match="log in"):
extract_claude_session("brave")
class TestAuthFromBrowserCLI:
def test_writes_env_after_validation(self, monkeypatch, tmp_path):
"""--from-browser extracts, validates live (mocked), and writes .env."""
from click.testing import CliRunner
from src.main import cli
from src.cache import Cache
cache = Cache(tmp_path / "cache")
cache.acknowledge_tos()
_fake_bc3(monkeypatch, {
"chatgpt.com": {
"__Secure-next-auth.session-token.0": "eyJtok0",
"__Secure-next-auth.session-token.1": "tok1",
},
"claude.ai": {"sessionKey": "sk-ant-sid01-abc"},
})
# Stub out the live validation calls.
import src.providers.chatgpt as chatgpt_mod
import src.providers.claude as claude_mod
monkeypatch.setattr(
chatgpt_mod.ChatGPTProvider, "_fetch_access_token", lambda self: "at"
)
monkeypatch.setattr(
claude_mod.ClaudeProvider,
"list_conversations",
lambda self, offset=0, limit=100: [],
)
runner = CliRunner(mix_stderr=True)
with runner.isolated_filesystem(temp_dir=tmp_path):
result = runner.invoke(
cli,
["--no-log-file", "auth", "--from-browser"],
env={"CACHE_DIR": str(tmp_path / "cache")},
)
assert result.exit_code == 0, result.output
from pathlib import Path
env_text = Path(".env").read_text()
assert "CHATGPT_SESSION_TOKEN=eyJtok0" in env_text
assert "CHATGPT_SESSION_TOKEN_1=tok1" in env_text
assert "CLAUDE_SESSION_KEY=sk-ant-sid01-abc" in env_text
assert "2 provider(s) configured" in result.output
def test_validation_failure_leaves_env_untouched(self, monkeypatch, tmp_path):
from click.testing import CliRunner
from src.main import cli
from src.cache import Cache
from src.providers.base import ProviderError
cache = Cache(tmp_path / "cache")
cache.acknowledge_tos()
_fake_bc3(monkeypatch, {
"chatgpt.com": {"__Secure-next-auth.session-token.0": "eyJstale"},
"claude.ai": {},
})
import src.providers.chatgpt as chatgpt_mod
def _fail(self):
raise ProviderError("chatgpt", "auth", RuntimeError("401"))
monkeypatch.setattr(chatgpt_mod.ChatGPTProvider, "_fetch_access_token", _fail)
runner = CliRunner(mix_stderr=True)
with runner.isolated_filesystem(temp_dir=tmp_path):
result = runner.invoke(
cli,
["--no-log-file", "auth", "--from-browser", "brave"],
env={"CACHE_DIR": str(tmp_path / "cache")},
)
import pathlib
env_exists = pathlib.Path(".env").exists()
assert result.exit_code == 1
assert "failed live validation" in result.output
assert not env_exists