code-puppy 0.0.214__py3-none-any.whl → 0.0.366__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code_puppy/__init__.py +7 -1
- code_puppy/agents/__init__.py +2 -0
- code_puppy/agents/agent_c_reviewer.py +59 -6
- code_puppy/agents/agent_code_puppy.py +7 -1
- code_puppy/agents/agent_code_reviewer.py +12 -2
- code_puppy/agents/agent_cpp_reviewer.py +73 -6
- code_puppy/agents/agent_creator_agent.py +45 -4
- code_puppy/agents/agent_golang_reviewer.py +92 -3
- code_puppy/agents/agent_javascript_reviewer.py +101 -8
- code_puppy/agents/agent_manager.py +81 -4
- code_puppy/agents/agent_pack_leader.py +383 -0
- code_puppy/agents/agent_planning.py +163 -0
- code_puppy/agents/agent_python_programmer.py +165 -0
- code_puppy/agents/agent_python_reviewer.py +28 -6
- code_puppy/agents/agent_qa_expert.py +98 -6
- code_puppy/agents/agent_qa_kitten.py +12 -7
- code_puppy/agents/agent_security_auditor.py +113 -3
- code_puppy/agents/agent_terminal_qa.py +323 -0
- code_puppy/agents/agent_typescript_reviewer.py +106 -7
- code_puppy/agents/base_agent.py +802 -176
- code_puppy/agents/event_stream_handler.py +350 -0
- code_puppy/agents/pack/__init__.py +34 -0
- code_puppy/agents/pack/bloodhound.py +304 -0
- code_puppy/agents/pack/husky.py +321 -0
- code_puppy/agents/pack/retriever.py +393 -0
- code_puppy/agents/pack/shepherd.py +348 -0
- code_puppy/agents/pack/terrier.py +287 -0
- code_puppy/agents/pack/watchdog.py +367 -0
- code_puppy/agents/prompt_reviewer.py +145 -0
- code_puppy/agents/subagent_stream_handler.py +276 -0
- code_puppy/api/__init__.py +13 -0
- code_puppy/api/app.py +169 -0
- code_puppy/api/main.py +21 -0
- code_puppy/api/pty_manager.py +446 -0
- code_puppy/api/routers/__init__.py +12 -0
- code_puppy/api/routers/agents.py +36 -0
- code_puppy/api/routers/commands.py +217 -0
- code_puppy/api/routers/config.py +74 -0
- code_puppy/api/routers/sessions.py +232 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +142 -4
- code_puppy/chatgpt_codex_client.py +283 -0
- code_puppy/claude_cache_client.py +586 -0
- code_puppy/cli_runner.py +916 -0
- code_puppy/command_line/add_model_menu.py +1079 -0
- code_puppy/command_line/agent_menu.py +395 -0
- code_puppy/command_line/attachments.py +10 -5
- code_puppy/command_line/autosave_menu.py +605 -0
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +520 -0
- code_puppy/command_line/command_handler.py +176 -738
- code_puppy/command_line/command_registry.py +150 -0
- code_puppy/command_line/config_commands.py +715 -0
- code_puppy/command_line/core_commands.py +792 -0
- code_puppy/command_line/diff_menu.py +863 -0
- code_puppy/command_line/load_context_completion.py +15 -22
- code_puppy/command_line/mcp/base.py +0 -3
- code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
- code_puppy/command_line/mcp/custom_server_form.py +688 -0
- code_puppy/command_line/mcp/custom_server_installer.py +195 -0
- code_puppy/command_line/mcp/edit_command.py +148 -0
- code_puppy/command_line/mcp/handler.py +9 -4
- code_puppy/command_line/mcp/help_command.py +6 -5
- code_puppy/command_line/mcp/install_command.py +15 -26
- code_puppy/command_line/mcp/install_menu.py +685 -0
- code_puppy/command_line/mcp/list_command.py +2 -2
- code_puppy/command_line/mcp/logs_command.py +174 -65
- code_puppy/command_line/mcp/remove_command.py +2 -2
- code_puppy/command_line/mcp/restart_command.py +12 -4
- code_puppy/command_line/mcp/search_command.py +16 -10
- code_puppy/command_line/mcp/start_all_command.py +18 -6
- code_puppy/command_line/mcp/start_command.py +47 -25
- code_puppy/command_line/mcp/status_command.py +4 -5
- code_puppy/command_line/mcp/stop_all_command.py +7 -1
- code_puppy/command_line/mcp/stop_command.py +8 -4
- code_puppy/command_line/mcp/test_command.py +2 -2
- code_puppy/command_line/mcp/wizard_utils.py +20 -16
- code_puppy/command_line/mcp_completion.py +174 -0
- code_puppy/command_line/model_picker_completion.py +75 -25
- code_puppy/command_line/model_settings_menu.py +884 -0
- code_puppy/command_line/motd.py +14 -8
- code_puppy/command_line/onboarding_slides.py +179 -0
- code_puppy/command_line/onboarding_wizard.py +340 -0
- code_puppy/command_line/pin_command_completion.py +329 -0
- code_puppy/command_line/prompt_toolkit_completion.py +463 -63
- code_puppy/command_line/session_commands.py +296 -0
- code_puppy/command_line/utils.py +54 -0
- code_puppy/config.py +898 -112
- code_puppy/error_logging.py +118 -0
- code_puppy/gemini_code_assist.py +385 -0
- code_puppy/gemini_model.py +602 -0
- code_puppy/http_utils.py +210 -148
- code_puppy/keymap.py +128 -0
- code_puppy/main.py +5 -698
- code_puppy/mcp_/__init__.py +17 -0
- code_puppy/mcp_/async_lifecycle.py +35 -4
- code_puppy/mcp_/blocking_startup.py +70 -43
- code_puppy/mcp_/captured_stdio_server.py +2 -2
- code_puppy/mcp_/config_wizard.py +4 -4
- code_puppy/mcp_/dashboard.py +15 -6
- code_puppy/mcp_/managed_server.py +65 -38
- code_puppy/mcp_/manager.py +146 -52
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/mcp_/registry.py +6 -6
- code_puppy/mcp_/server_registry_catalog.py +24 -5
- code_puppy/messaging/__init__.py +199 -2
- code_puppy/messaging/bus.py +610 -0
- code_puppy/messaging/commands.py +167 -0
- code_puppy/messaging/markdown_patches.py +57 -0
- code_puppy/messaging/message_queue.py +17 -48
- code_puppy/messaging/messages.py +500 -0
- code_puppy/messaging/queue_console.py +1 -24
- code_puppy/messaging/renderers.py +43 -146
- code_puppy/messaging/rich_renderer.py +1027 -0
- code_puppy/messaging/spinner/__init__.py +21 -5
- code_puppy/messaging/spinner/console_spinner.py +86 -51
- code_puppy/messaging/subagent_console.py +461 -0
- code_puppy/model_factory.py +634 -83
- code_puppy/model_utils.py +167 -0
- code_puppy/models.json +66 -68
- code_puppy/models_dev_api.json +1 -0
- code_puppy/models_dev_parser.py +592 -0
- code_puppy/plugins/__init__.py +164 -10
- code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
- code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
- code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy/plugins/antigravity_oauth/constants.py +136 -0
- code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
- code_puppy/plugins/antigravity_oauth/storage.py +271 -0
- code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
- code_puppy/plugins/antigravity_oauth/token.py +167 -0
- code_puppy/plugins/antigravity_oauth/transport.py +767 -0
- code_puppy/plugins/antigravity_oauth/utils.py +169 -0
- code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
- code_puppy/plugins/chatgpt_oauth/config.py +52 -0
- code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
- code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
- code_puppy/plugins/claude_code_oauth/README.md +167 -0
- code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
- code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
- code_puppy/plugins/claude_code_oauth/config.py +50 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
- code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_puppy/plugins/claude_code_oauth/utils.py +518 -0
- code_puppy/plugins/customizable_commands/__init__.py +0 -0
- code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
- code_puppy/plugins/example_custom_command/README.md +280 -0
- code_puppy/plugins/example_custom_command/register_callbacks.py +2 -2
- code_puppy/plugins/file_permission_handler/__init__.py +4 -0
- code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
- code_puppy/plugins/frontend_emitter/__init__.py +25 -0
- code_puppy/plugins/frontend_emitter/emitter.py +121 -0
- code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
- code_puppy/plugins/oauth_puppy_html.py +228 -0
- code_puppy/plugins/shell_safety/__init__.py +6 -0
- code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_puppy/plugins/shell_safety/command_cache.py +156 -0
- code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
- code_puppy/prompts/antigravity_system_prompt.md +1 -0
- code_puppy/prompts/codex_system_prompt.md +310 -0
- code_puppy/pydantic_patches.py +131 -0
- code_puppy/reopenable_async_client.py +8 -8
- code_puppy/round_robin_model.py +9 -12
- code_puppy/session_storage.py +2 -1
- code_puppy/status_display.py +21 -4
- code_puppy/summarization_agent.py +41 -13
- code_puppy/terminal_utils.py +418 -0
- code_puppy/tools/__init__.py +37 -1
- code_puppy/tools/agent_tools.py +536 -52
- code_puppy/tools/browser/__init__.py +37 -0
- code_puppy/tools/browser/browser_control.py +19 -23
- code_puppy/tools/browser/browser_interactions.py +41 -48
- code_puppy/tools/browser/browser_locators.py +36 -38
- code_puppy/tools/browser/browser_manager.py +316 -0
- code_puppy/tools/browser/browser_navigation.py +16 -16
- code_puppy/tools/browser/browser_screenshot.py +79 -143
- code_puppy/tools/browser/browser_scripts.py +32 -42
- code_puppy/tools/browser/browser_workflows.py +44 -27
- code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy/tools/browser/terminal_command_tools.py +521 -0
- code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
- code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy/tools/command_runner.py +930 -147
- code_puppy/tools/common.py +1113 -5
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/file_modifications.py +288 -89
- code_puppy/tools/file_operations.py +226 -154
- code_puppy/tools/subagent_context.py +158 -0
- code_puppy/uvx_detection.py +242 -0
- code_puppy/version_checker.py +30 -11
- code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
- code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
- {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/METADATA +149 -75
- code_puppy-0.0.366.dist-info/RECORD +217 -0
- {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
- code_puppy/command_line/mcp/add_command.py +0 -183
- code_puppy/messaging/spinner/textual_spinner.py +0 -106
- code_puppy/tools/browser/camoufox_manager.py +0 -216
- code_puppy/tools/browser/vqa_agent.py +0 -70
- code_puppy/tui/__init__.py +0 -10
- code_puppy/tui/app.py +0 -1105
- code_puppy/tui/components/__init__.py +0 -21
- code_puppy/tui/components/chat_view.py +0 -551
- code_puppy/tui/components/command_history_modal.py +0 -218
- code_puppy/tui/components/copy_button.py +0 -139
- code_puppy/tui/components/custom_widgets.py +0 -63
- code_puppy/tui/components/human_input_modal.py +0 -175
- code_puppy/tui/components/input_area.py +0 -167
- code_puppy/tui/components/sidebar.py +0 -309
- code_puppy/tui/components/status_bar.py +0 -185
- code_puppy/tui/messages.py +0 -27
- code_puppy/tui/models/__init__.py +0 -8
- code_puppy/tui/models/chat_message.py +0 -25
- code_puppy/tui/models/command_history.py +0 -89
- code_puppy/tui/models/enums.py +0 -24
- code_puppy/tui/screens/__init__.py +0 -17
- code_puppy/tui/screens/autosave_picker.py +0 -175
- code_puppy/tui/screens/help.py +0 -130
- code_puppy/tui/screens/mcp_install_wizard.py +0 -803
- code_puppy/tui/screens/settings.py +0 -306
- code_puppy/tui/screens/tools.py +0 -74
- code_puppy/tui_state.py +0 -55
- code_puppy-0.0.214.data/data/code_puppy/models.json +0 -112
- code_puppy-0.0.214.dist-info/RECORD +0 -131
- {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Basic tests for ChatGPT OAuth plugin.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from unittest.mock import MagicMock, patch
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from code_puppy.plugins.chatgpt_oauth import config, utils
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_config_paths():
|
|
14
|
+
"""Test configuration path helpers."""
|
|
15
|
+
token_path = config.get_token_storage_path()
|
|
16
|
+
assert token_path.name == "chatgpt_oauth.json"
|
|
17
|
+
# XDG paths use "code_puppy" (without dot) in ~/.local/share or ~/.config
|
|
18
|
+
assert "code_puppy" in str(token_path)
|
|
19
|
+
|
|
20
|
+
config_dir = config.get_config_dir()
|
|
21
|
+
# Default is ~/.code_puppy; XDG paths only used when XDG env vars are set
|
|
22
|
+
assert config_dir.name in ("code_puppy", ".code_puppy")
|
|
23
|
+
|
|
24
|
+
chatgpt_models = config.get_chatgpt_models_path()
|
|
25
|
+
assert chatgpt_models.name == "chatgpt_models.json"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_oauth_config():
|
|
29
|
+
"""Test OAuth configuration values."""
|
|
30
|
+
assert config.CHATGPT_OAUTH_CONFIG["issuer"] == "https://auth.openai.com"
|
|
31
|
+
assert config.CHATGPT_OAUTH_CONFIG["client_id"] == "app_EMoamEEZ73f0CkXaXp7hrann"
|
|
32
|
+
assert config.CHATGPT_OAUTH_CONFIG["prefix"] == "chatgpt-"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_jwt_parsing_with_nested_org():
|
|
36
|
+
"""Test JWT parsing with nested organization structure like the user's payload."""
|
|
37
|
+
# This simulates the user's JWT payload structure
|
|
38
|
+
mock_claims = {
|
|
39
|
+
"aud": ["app_EMoamEEZ73f0CkXaXp7hrann"],
|
|
40
|
+
"auth_provider": "google",
|
|
41
|
+
"email": "mike.pfaf fenberger@gmail.com",
|
|
42
|
+
"https://api.openai.com/auth": {
|
|
43
|
+
"chatgpt_account_id": "d1844a91-9aac-419b-903e-f6a99c76f163",
|
|
44
|
+
"organizations": [
|
|
45
|
+
{
|
|
46
|
+
"id": "org-iydWjnSxSr51VuYhDVMDte5",
|
|
47
|
+
"is_default": True,
|
|
48
|
+
"role": "owner",
|
|
49
|
+
"title": "Personal",
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"groups": ["api-data-sharing-incentives-program", "verified-organization"],
|
|
53
|
+
},
|
|
54
|
+
"sub": "google-oauth2|107692466937587138174",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Test the org extraction logic
|
|
58
|
+
auth_claims = mock_claims.get("https://api.openai.com/auth", {})
|
|
59
|
+
organizations = auth_claims.get("organizations", [])
|
|
60
|
+
|
|
61
|
+
org_id = None
|
|
62
|
+
if organizations:
|
|
63
|
+
default_org = next(
|
|
64
|
+
(org for org in organizations if org.get("is_default")), organizations[0]
|
|
65
|
+
)
|
|
66
|
+
org_id = default_org.get("id")
|
|
67
|
+
|
|
68
|
+
assert org_id == "org-iydWjnSxSr51VuYhDVMDte5"
|
|
69
|
+
|
|
70
|
+
# Test fallback to top-level org_id (should not happen in this case)
|
|
71
|
+
if not org_id:
|
|
72
|
+
org_id = mock_claims.get("organization_id")
|
|
73
|
+
|
|
74
|
+
assert org_id == "org-iydWjnSxSr51VuYhDVMDte5"
|
|
75
|
+
assert config.CHATGPT_OAUTH_CONFIG["required_port"] == 1455
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_code_verifier_generation():
|
|
79
|
+
"""Test PKCE code verifier generation."""
|
|
80
|
+
verifier = utils._generate_code_verifier()
|
|
81
|
+
assert isinstance(verifier, str)
|
|
82
|
+
assert len(verifier) > 50 # Should be long
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_code_challenge_computation():
|
|
86
|
+
"""Test PKCE code challenge computation."""
|
|
87
|
+
verifier = "test_verifier_string"
|
|
88
|
+
challenge = utils._compute_code_challenge(verifier)
|
|
89
|
+
assert isinstance(challenge, str)
|
|
90
|
+
assert len(challenge) > 0
|
|
91
|
+
# Should be URL-safe base64
|
|
92
|
+
assert all(
|
|
93
|
+
c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
|
94
|
+
for c in challenge
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_prepare_oauth_context():
|
|
99
|
+
"""Test OAuth context preparation."""
|
|
100
|
+
context = utils.prepare_oauth_context()
|
|
101
|
+
assert context.state
|
|
102
|
+
assert context.code_verifier
|
|
103
|
+
assert context.code_challenge
|
|
104
|
+
assert context.created_at > 0
|
|
105
|
+
assert context.redirect_uri is None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_assign_redirect_uri():
|
|
109
|
+
"""Test redirect URI assignment."""
|
|
110
|
+
context = utils.prepare_oauth_context()
|
|
111
|
+
redirect_uri = utils.assign_redirect_uri(context, 1455)
|
|
112
|
+
assert redirect_uri == "http://localhost:1455/auth/callback"
|
|
113
|
+
assert context.redirect_uri == redirect_uri
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_build_authorization_url():
|
|
117
|
+
"""Test authorization URL building."""
|
|
118
|
+
context = utils.prepare_oauth_context()
|
|
119
|
+
utils.assign_redirect_uri(context, 1455)
|
|
120
|
+
auth_url = utils.build_authorization_url(context)
|
|
121
|
+
|
|
122
|
+
assert auth_url.startswith("https://auth.openai.com/oauth/authorize?")
|
|
123
|
+
assert "response_type=code" in auth_url
|
|
124
|
+
assert "client_id=" in auth_url
|
|
125
|
+
assert "redirect_uri=" in auth_url
|
|
126
|
+
assert "code_challenge=" in auth_url
|
|
127
|
+
assert "code_challenge_method=S256" in auth_url
|
|
128
|
+
assert f"state={context.state}" in auth_url
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_parse_jwt_claims():
|
|
132
|
+
"""Test JWT claims parsing."""
|
|
133
|
+
# Valid JWT structure (header.payload.signature)
|
|
134
|
+
import base64
|
|
135
|
+
|
|
136
|
+
payload = base64.urlsafe_b64encode(json.dumps({"sub": "user123"}).encode()).decode()
|
|
137
|
+
token = f"header.{payload}.signature"
|
|
138
|
+
|
|
139
|
+
claims = utils.parse_jwt_claims(token)
|
|
140
|
+
assert claims is not None
|
|
141
|
+
assert claims["sub"] == "user123"
|
|
142
|
+
|
|
143
|
+
# Invalid token
|
|
144
|
+
assert utils.parse_jwt_claims("") is None
|
|
145
|
+
assert utils.parse_jwt_claims("invalid") is None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_save_and_load_tokens(tmp_path):
|
|
149
|
+
"""Test token storage and retrieval."""
|
|
150
|
+
with patch.object(
|
|
151
|
+
config, "get_token_storage_path", return_value=tmp_path / "tokens.json"
|
|
152
|
+
):
|
|
153
|
+
tokens = {
|
|
154
|
+
"access_token": "test_access",
|
|
155
|
+
"refresh_token": "test_refresh",
|
|
156
|
+
"api_key": "sk-test",
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# Save tokens
|
|
160
|
+
assert utils.save_tokens(tokens)
|
|
161
|
+
|
|
162
|
+
# Load tokens
|
|
163
|
+
loaded = utils.load_stored_tokens()
|
|
164
|
+
assert loaded == tokens
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_save_and_load_chatgpt_models(tmp_path):
|
|
168
|
+
"""Test ChatGPT models configuration."""
|
|
169
|
+
with patch.object(
|
|
170
|
+
config, "get_chatgpt_models_path", return_value=tmp_path / "chatgpt_models.json"
|
|
171
|
+
):
|
|
172
|
+
models = {
|
|
173
|
+
"chatgpt-gpt-4o": {
|
|
174
|
+
"type": "openai",
|
|
175
|
+
"name": "gpt-4o",
|
|
176
|
+
"oauth_source": "chatgpt-oauth-plugin",
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# Save models
|
|
181
|
+
assert utils.save_chatgpt_models(models)
|
|
182
|
+
|
|
183
|
+
# Load models
|
|
184
|
+
loaded = utils.load_chatgpt_models()
|
|
185
|
+
assert loaded == models
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_remove_chatgpt_models(tmp_path):
|
|
189
|
+
"""Test removal of ChatGPT models from config."""
|
|
190
|
+
with patch.object(
|
|
191
|
+
config, "get_chatgpt_models_path", return_value=tmp_path / "chatgpt_models.json"
|
|
192
|
+
):
|
|
193
|
+
models = {
|
|
194
|
+
"chatgpt-gpt-4o": {
|
|
195
|
+
"type": "openai",
|
|
196
|
+
"oauth_source": "chatgpt-oauth-plugin",
|
|
197
|
+
},
|
|
198
|
+
"claude-3-opus": {
|
|
199
|
+
"type": "anthropic",
|
|
200
|
+
"oauth_source": "other",
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
utils.save_chatgpt_models(models)
|
|
204
|
+
|
|
205
|
+
# Remove only ChatGPT models
|
|
206
|
+
removed_count = utils.remove_chatgpt_models()
|
|
207
|
+
assert removed_count == 1
|
|
208
|
+
|
|
209
|
+
# Verify only ChatGPT model was removed
|
|
210
|
+
remaining = utils.load_chatgpt_models()
|
|
211
|
+
assert "chatgpt-gpt-4o" not in remaining
|
|
212
|
+
assert "claude-3-opus" in remaining
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@patch("code_puppy.plugins.chatgpt_oauth.utils.requests.post")
|
|
216
|
+
def test_exchange_code_for_tokens(mock_post):
|
|
217
|
+
"""Test authorization code exchange."""
|
|
218
|
+
mock_response = MagicMock()
|
|
219
|
+
mock_response.status_code = 200
|
|
220
|
+
mock_response.json.return_value = {
|
|
221
|
+
"access_token": "test_access",
|
|
222
|
+
"refresh_token": "test_refresh",
|
|
223
|
+
"id_token": "test_id",
|
|
224
|
+
}
|
|
225
|
+
mock_post.return_value = mock_response
|
|
226
|
+
|
|
227
|
+
context = utils.prepare_oauth_context()
|
|
228
|
+
utils.assign_redirect_uri(context, 1455)
|
|
229
|
+
|
|
230
|
+
tokens = utils.exchange_code_for_tokens("test_code", context)
|
|
231
|
+
assert tokens is not None
|
|
232
|
+
assert tokens["access_token"] == "test_access"
|
|
233
|
+
assert "last_refresh" in tokens
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@patch("code_puppy.plugins.chatgpt_oauth.utils.requests.get")
|
|
237
|
+
def test_fetch_chatgpt_models(mock_get):
|
|
238
|
+
"""Test fetching models from ChatGPT Codex API."""
|
|
239
|
+
mock_response = MagicMock()
|
|
240
|
+
mock_response.status_code = 200
|
|
241
|
+
# New response format uses "models" key with "slug" field
|
|
242
|
+
mock_response.json.return_value = {
|
|
243
|
+
"models": [
|
|
244
|
+
{"slug": "gpt-4o"},
|
|
245
|
+
{"slug": "gpt-3.5-turbo"},
|
|
246
|
+
{"slug": "o1-preview"},
|
|
247
|
+
{"slug": "codex-mini"},
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
mock_get.return_value = mock_response
|
|
251
|
+
|
|
252
|
+
models = utils.fetch_chatgpt_models("test_access_token", "test_account_id")
|
|
253
|
+
assert models is not None
|
|
254
|
+
assert "gpt-4o" in models
|
|
255
|
+
assert "gpt-3.5-turbo" in models
|
|
256
|
+
assert "o1-preview" in models
|
|
257
|
+
assert "codex-mini" in models
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@patch("code_puppy.plugins.chatgpt_oauth.utils.requests.get")
|
|
261
|
+
def test_fetch_chatgpt_models_fallback(mock_get):
|
|
262
|
+
"""Test that fetch_chatgpt_models returns default list on API failure."""
|
|
263
|
+
mock_response = MagicMock()
|
|
264
|
+
mock_response.status_code = 404
|
|
265
|
+
mock_response.text = '{"detail":"Not Found"}'
|
|
266
|
+
mock_get.return_value = mock_response
|
|
267
|
+
|
|
268
|
+
models = utils.fetch_chatgpt_models("test_access_token", "test_account_id")
|
|
269
|
+
assert models is not None
|
|
270
|
+
# Should return default models
|
|
271
|
+
assert "gpt-5.2" in models
|
|
272
|
+
assert "gpt-4o" in models
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def test_add_models_to_chatgpt_config(tmp_path):
|
|
276
|
+
"""Test adding models to chatgpt_models.json."""
|
|
277
|
+
with patch.object(
|
|
278
|
+
config, "get_chatgpt_models_path", return_value=tmp_path / "chatgpt_models.json"
|
|
279
|
+
):
|
|
280
|
+
models = ["gpt-4o", "gpt-3.5-turbo"]
|
|
281
|
+
|
|
282
|
+
assert utils.add_models_to_extra_config(models)
|
|
283
|
+
|
|
284
|
+
loaded = utils.load_chatgpt_models()
|
|
285
|
+
assert "chatgpt-gpt-4o" in loaded
|
|
286
|
+
assert "chatgpt-gpt-3.5-turbo" in loaded
|
|
287
|
+
assert loaded["chatgpt-gpt-4o"]["type"] == "chatgpt_oauth"
|
|
288
|
+
assert loaded["chatgpt-gpt-4o"]["name"] == "gpt-4o"
|
|
289
|
+
assert loaded["chatgpt-gpt-4o"]["oauth_source"] == "chatgpt-oauth-plugin"
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__":
|
|
293
|
+
pytest.main([__file__, "-v"])
|