connectonion 0.4.12__py3-none-any.whl → 0.5.1__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.
- connectonion/__init__.py +11 -5
- connectonion/agent.py +44 -42
- connectonion/cli/commands/init.py +1 -1
- connectonion/cli/commands/project_cmd_lib.py +4 -4
- connectonion/cli/commands/reset_commands.py +1 -1
- connectonion/cli/docs/co-vibecoding-principles-docs-contexts-all-in-one.md +15 -11
- connectonion/cli/templates/minimal/agent.py +2 -2
- connectonion/console.py +55 -3
- connectonion/events.py +96 -17
- connectonion/llm.py +21 -3
- connectonion/logger.py +289 -0
- connectonion/prompt_files/eval_expected.md +12 -0
- connectonion/tool_executor.py +43 -32
- connectonion/usage.py +4 -0
- connectonion/useful_events_handlers/reflect.py +13 -9
- connectonion/useful_plugins/__init__.py +2 -1
- connectonion/useful_plugins/calendar_plugin.py +2 -2
- connectonion/useful_plugins/eval.py +130 -0
- connectonion/useful_plugins/gmail_plugin.py +4 -4
- connectonion/useful_plugins/image_result_formatter.py +4 -3
- connectonion/useful_plugins/re_act.py +14 -56
- connectonion/useful_plugins/shell_approval.py +2 -2
- connectonion/useful_tools/memory.py +4 -0
- {connectonion-0.4.12.dist-info → connectonion-0.5.1.dist-info}/METADATA +49 -49
- {connectonion-0.4.12.dist-info → connectonion-0.5.1.dist-info}/RECORD +27 -71
- {connectonion-0.4.12.dist-info → connectonion-0.5.1.dist-info}/WHEEL +1 -2
- connectonion/cli/templates/email-agent/.env.example +0 -23
- connectonion/cli/templates/email-agent/README.md +0 -240
- connectonion/cli/templates/email-agent/agent.py +0 -374
- connectonion/cli/templates/email-agent/demo.py +0 -71
- connectonion/cli/templates/meta-agent/.env.example +0 -11
- connectonion/cli/templates/minimal/.env.example +0 -5
- connectonion/cli/templates/playwright/.env.example +0 -5
- connectonion-0.4.12.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -0
- tests/cli/__init__.py +0 -1
- tests/cli/argparse_runner.py +0 -85
- tests/cli/conftest.py +0 -5
- tests/cli/test_browser_cli.py +0 -61
- tests/cli/test_cli.py +0 -143
- tests/cli/test_cli_auth_google.py +0 -344
- tests/cli/test_cli_auth_microsoft.py +0 -256
- tests/cli/test_cli_create.py +0 -283
- tests/cli/test_cli_help.py +0 -200
- tests/cli/test_cli_init.py +0 -318
- tests/conftest.py +0 -283
- tests/debug_gemini_models.py +0 -23
- tests/fixtures/__init__.py +0 -1
- tests/fixtures/test_tools.py +0 -112
- tests/fixtures/trust_fixtures.py +0 -257
- tests/real_api/__init__.py +0 -0
- tests/real_api/conftest.py +0 -9
- tests/real_api/test_llm_do.py +0 -174
- tests/real_api/test_llm_do_comprehensive.py +0 -527
- tests/real_api/test_production_client.py +0 -94
- tests/real_api/test_real_anthropic.py +0 -100
- tests/real_api/test_real_api.py +0 -113
- tests/real_api/test_real_auth.py +0 -130
- tests/real_api/test_real_email.py +0 -95
- tests/real_api/test_real_gemini.py +0 -96
- tests/real_api/test_real_llm_do.py +0 -81
- tests/real_api/test_real_managed.py +0 -208
- tests/real_api/test_real_multi_llm.py +0 -454
- tests/real_api/test_real_openai.py +0 -100
- tests/real_api/test_responses_parse.py +0 -88
- tests/test_diff_writer.py +0 -126
- tests/test_events.py +0 -677
- tests/test_gemini_co.py +0 -70
- tests/test_image_result_formatter.py +0 -88
- tests/test_plugin_system.py +0 -110
- tests/utils/__init__.py +0 -1
- tests/utils/config_helpers.py +0 -188
- tests/utils/mock_helpers.py +0 -237
- {connectonion-0.4.12.dist-info → connectonion-0.5.1.dist-info}/entry_points.txt +0 -0
tests/cli/test_cli.py
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
"""Simplified tests for CLI init command - focusing on core behavior."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import tempfile
|
|
5
|
-
import shutil
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
import pytest
|
|
8
|
-
|
|
9
|
-
from .argparse_runner import ArgparseCliRunner
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestCliInit:
|
|
13
|
-
"""Test the co init command."""
|
|
14
|
-
|
|
15
|
-
def setup_method(self):
|
|
16
|
-
"""Setup test environment."""
|
|
17
|
-
self.runner = ArgparseCliRunner()
|
|
18
|
-
|
|
19
|
-
def test_init_creates_working_agent(self):
|
|
20
|
-
"""Test that init creates a working agent setup."""
|
|
21
|
-
with self.runner.isolated_filesystem():
|
|
22
|
-
# Import here to avoid issues before installation
|
|
23
|
-
from connectonion.cli.main import cli
|
|
24
|
-
|
|
25
|
-
# Run init with template to create agent.py
|
|
26
|
-
result = self.runner.invoke(cli, ['init', '--template', 'minimal'])
|
|
27
|
-
assert result.exit_code == 0
|
|
28
|
-
|
|
29
|
-
# Check core files exist
|
|
30
|
-
assert os.path.exists('agent.py')
|
|
31
|
-
assert os.path.exists('.env') # CLI creates .env, not .env.example
|
|
32
|
-
assert os.path.exists('.co/config.toml')
|
|
33
|
-
|
|
34
|
-
# Verify agent.py is valid Python
|
|
35
|
-
with open('agent.py') as f:
|
|
36
|
-
code = f.read()
|
|
37
|
-
compile(code, 'agent.py', 'exec')
|
|
38
|
-
|
|
39
|
-
# Check agent.py references ConnectOnion
|
|
40
|
-
assert 'from connectonion import Agent' in code
|
|
41
|
-
|
|
42
|
-
# Check config.toml has correct structure
|
|
43
|
-
import toml
|
|
44
|
-
with open('.co/config.toml') as f:
|
|
45
|
-
config = toml.load(f)
|
|
46
|
-
assert 'project' in config
|
|
47
|
-
assert 'cli' in config
|
|
48
|
-
|
|
49
|
-
def test_init_templates(self):
|
|
50
|
-
"""Test that different templates create appropriate agents."""
|
|
51
|
-
with self.runner.isolated_filesystem():
|
|
52
|
-
from connectonion.cli.main import cli
|
|
53
|
-
|
|
54
|
-
# Test with a template
|
|
55
|
-
result = self.runner.invoke(cli, ['init', '--template', 'minimal'])
|
|
56
|
-
assert result.exit_code == 0
|
|
57
|
-
|
|
58
|
-
with open('agent.py') as f:
|
|
59
|
-
content = f.read()
|
|
60
|
-
|
|
61
|
-
# Should have basic agent structure
|
|
62
|
-
assert 'from connectonion import Agent' in content
|
|
63
|
-
|
|
64
|
-
def test_init_in_non_empty_directory(self):
|
|
65
|
-
"""Test that init asks for confirmation in non-empty directories."""
|
|
66
|
-
with self.runner.isolated_filesystem():
|
|
67
|
-
from connectonion.cli.main import cli
|
|
68
|
-
|
|
69
|
-
# Create existing file
|
|
70
|
-
Path('existing.txt').write_text('existing content')
|
|
71
|
-
|
|
72
|
-
# Should ask for confirmation and abort when user says no
|
|
73
|
-
result = self.runner.invoke(cli, ['init'], input='n\n')
|
|
74
|
-
# Check that agent.py was NOT created
|
|
75
|
-
if result.exit_code == 0:
|
|
76
|
-
# If exit code is 0, user declined, so no agent.py
|
|
77
|
-
assert not os.path.exists('agent.py') or result.exit_code != 0
|
|
78
|
-
|
|
79
|
-
# Should proceed when user confirms
|
|
80
|
-
result = self.runner.invoke(cli, ['init', '--template', 'minimal'], input='y\n')
|
|
81
|
-
assert result.exit_code == 0
|
|
82
|
-
assert os.path.exists('agent.py')
|
|
83
|
-
assert os.path.exists('existing.txt') # Preserves existing files
|
|
84
|
-
|
|
85
|
-
def test_init_never_overwrites(self):
|
|
86
|
-
"""Test that init never overwrites existing agent.py."""
|
|
87
|
-
with self.runner.isolated_filesystem():
|
|
88
|
-
from connectonion.cli.main import cli
|
|
89
|
-
|
|
90
|
-
# Create existing agent.py
|
|
91
|
-
Path('agent.py').write_text('# My custom agent')
|
|
92
|
-
|
|
93
|
-
# Run init
|
|
94
|
-
result = self.runner.invoke(cli, ['init'], input='y\n')
|
|
95
|
-
|
|
96
|
-
# Should not overwrite
|
|
97
|
-
with open('agent.py') as f:
|
|
98
|
-
content = f.read()
|
|
99
|
-
assert content == '# My custom agent'
|
|
100
|
-
|
|
101
|
-
def test_init_with_git(self):
|
|
102
|
-
"""Test that init handles git repos properly."""
|
|
103
|
-
with self.runner.isolated_filesystem():
|
|
104
|
-
from connectonion.cli.main import cli
|
|
105
|
-
|
|
106
|
-
# Create .git directory
|
|
107
|
-
os.makedirs('.git')
|
|
108
|
-
|
|
109
|
-
# Run init (will need confirmation since .git makes it non-empty)
|
|
110
|
-
result = self.runner.invoke(cli, ['init'], input='y\n')
|
|
111
|
-
assert result.exit_code == 0
|
|
112
|
-
|
|
113
|
-
# Should create .gitignore
|
|
114
|
-
assert os.path.exists('.gitignore')
|
|
115
|
-
with open('.gitignore') as f:
|
|
116
|
-
content = f.read()
|
|
117
|
-
assert '.env' in content
|
|
118
|
-
assert '__pycache__' in content
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def _co_cli_works():
|
|
122
|
-
"""Check if 'co --version' actually runs successfully."""
|
|
123
|
-
import subprocess
|
|
124
|
-
if shutil.which('co') is None:
|
|
125
|
-
return False
|
|
126
|
-
result = subprocess.run(['co', '--version'], capture_output=True, text=True)
|
|
127
|
-
return result.returncode == 0
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@pytest.mark.skipif(
|
|
131
|
-
not _co_cli_works(),
|
|
132
|
-
reason="CLI not installed or not working"
|
|
133
|
-
)
|
|
134
|
-
class TestCliCommands:
|
|
135
|
-
"""Test actual CLI commands (requires installation)."""
|
|
136
|
-
|
|
137
|
-
def test_co_command_works(self):
|
|
138
|
-
"""Test that 'co' command is available after installation."""
|
|
139
|
-
import subprocess
|
|
140
|
-
result = subprocess.run(['co', '--version'], capture_output=True, text=True)
|
|
141
|
-
assert result.returncode == 0
|
|
142
|
-
# Check for version in output (not specific version number)
|
|
143
|
-
assert any(c.isdigit() for c in result.stdout)
|
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
"""Tests for co auth google CLI command."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import tempfile
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
import pytest
|
|
7
|
-
from unittest.mock import Mock, patch, MagicMock
|
|
8
|
-
|
|
9
|
-
from .argparse_runner import ArgparseCliRunner
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestAuthGoogleHelp:
|
|
13
|
-
"""Test help text for co auth google command."""
|
|
14
|
-
|
|
15
|
-
def setup_method(self):
|
|
16
|
-
"""Setup test environment."""
|
|
17
|
-
self.runner = ArgparseCliRunner()
|
|
18
|
-
|
|
19
|
-
def test_auth_help_shows_google_option(self):
|
|
20
|
-
"""Test that co auth --help mentions google service."""
|
|
21
|
-
from connectonion.cli.main import cli
|
|
22
|
-
|
|
23
|
-
result = self.runner.invoke(cli, ['auth', '--help'])
|
|
24
|
-
assert result.exit_code == 0
|
|
25
|
-
assert 'google' in result.output.lower()
|
|
26
|
-
|
|
27
|
-
@patch('connectonion.cli.commands.auth_commands._load_api_key')
|
|
28
|
-
def test_auth_google_requires_openonion_auth(self, mock_load_key):
|
|
29
|
-
"""Test that co auth google requires prior OpenOnion authentication."""
|
|
30
|
-
# Mock _load_api_key to return None (no API key found)
|
|
31
|
-
mock_load_key.return_value = None
|
|
32
|
-
|
|
33
|
-
with self.runner.isolated_filesystem():
|
|
34
|
-
from connectonion.cli.main import cli
|
|
35
|
-
|
|
36
|
-
# co auth google should fail without OPENONION_API_KEY
|
|
37
|
-
result = self.runner.invoke(cli, ['auth', 'google'])
|
|
38
|
-
assert 'Not authenticated with OpenOnion' in result.output
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class TestLoadApiKey:
|
|
42
|
-
"""Test the _load_api_key helper function."""
|
|
43
|
-
|
|
44
|
-
def test_load_api_key_from_env_var(self):
|
|
45
|
-
"""Test loading API key from environment variable."""
|
|
46
|
-
from connectonion.cli.commands.auth_commands import _load_api_key
|
|
47
|
-
|
|
48
|
-
with patch.dict(os.environ, {'OPENONION_API_KEY': 'test-key-123'}):
|
|
49
|
-
key = _load_api_key()
|
|
50
|
-
assert key == 'test-key-123'
|
|
51
|
-
|
|
52
|
-
def test_load_api_key_from_local_env(self):
|
|
53
|
-
"""Test loading API key from local .env file."""
|
|
54
|
-
from connectonion.cli.commands.auth_commands import _load_api_key
|
|
55
|
-
|
|
56
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
57
|
-
os.chdir(tmpdir)
|
|
58
|
-
|
|
59
|
-
# Create .env with API key
|
|
60
|
-
Path('.env').write_text('OPENONION_API_KEY=local-key-456\n')
|
|
61
|
-
|
|
62
|
-
# Clear environment variable
|
|
63
|
-
with patch.dict(os.environ, {}, clear=True):
|
|
64
|
-
key = _load_api_key()
|
|
65
|
-
assert key == 'local-key-456'
|
|
66
|
-
|
|
67
|
-
def test_load_api_key_from_global_keys_env(self):
|
|
68
|
-
"""Test loading API key from global ~/.co/keys.env."""
|
|
69
|
-
from connectonion.cli.commands.auth_commands import _load_api_key
|
|
70
|
-
|
|
71
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
72
|
-
os.chdir(tmpdir)
|
|
73
|
-
|
|
74
|
-
# Create mock ~/.co/keys.env
|
|
75
|
-
co_dir = Path(tmpdir) / '.co'
|
|
76
|
-
co_dir.mkdir()
|
|
77
|
-
keys_env = co_dir / 'keys.env'
|
|
78
|
-
keys_env.write_text('OPENONION_API_KEY=global-key-789\n')
|
|
79
|
-
|
|
80
|
-
# Mock Path.home() to return tmpdir
|
|
81
|
-
with patch('pathlib.Path.home', return_value=Path(tmpdir)):
|
|
82
|
-
with patch.dict(os.environ, {}, clear=True):
|
|
83
|
-
key = _load_api_key()
|
|
84
|
-
assert key == 'global-key-789'
|
|
85
|
-
|
|
86
|
-
def test_load_api_key_returns_none_when_not_found(self):
|
|
87
|
-
"""Test that _load_api_key returns None when no key found."""
|
|
88
|
-
from connectonion.cli.commands.auth_commands import _load_api_key
|
|
89
|
-
|
|
90
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
91
|
-
os.chdir(tmpdir)
|
|
92
|
-
|
|
93
|
-
# Mock Path.home() to return tmpdir (no keys.env)
|
|
94
|
-
with patch('pathlib.Path.home', return_value=Path(tmpdir)):
|
|
95
|
-
with patch.dict(os.environ, {}, clear=True):
|
|
96
|
-
key = _load_api_key()
|
|
97
|
-
assert key is None
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
class TestSaveGoogleToEnv:
|
|
101
|
-
"""Test the _save_google_to_env helper function."""
|
|
102
|
-
|
|
103
|
-
def test_save_google_credentials_to_new_env(self):
|
|
104
|
-
"""Test saving Google credentials to a new .env file."""
|
|
105
|
-
from connectonion.cli.commands.auth_commands import _save_google_to_env
|
|
106
|
-
|
|
107
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
108
|
-
env_file = Path(tmpdir) / '.env'
|
|
109
|
-
|
|
110
|
-
credentials = {
|
|
111
|
-
'access_token': 'ya29.test123',
|
|
112
|
-
'refresh_token': '1//0gtest456',
|
|
113
|
-
'expires_at': '2025-12-31T23:59:59',
|
|
114
|
-
'scopes': 'gmail.send,calendar.readonly',
|
|
115
|
-
'google_email': 'test@gmail.com'
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
_save_google_to_env(env_file, credentials)
|
|
119
|
-
|
|
120
|
-
# Verify file was created
|
|
121
|
-
assert env_file.exists()
|
|
122
|
-
|
|
123
|
-
# Verify content
|
|
124
|
-
content = env_file.read_text()
|
|
125
|
-
assert 'GOOGLE_ACCESS_TOKEN=ya29.test123' in content
|
|
126
|
-
assert 'GOOGLE_REFRESH_TOKEN=1//0gtest456' in content
|
|
127
|
-
assert 'GOOGLE_TOKEN_EXPIRES_AT=2025-12-31T23:59:59' in content
|
|
128
|
-
assert 'GOOGLE_SCOPES=gmail.send,calendar.readonly' in content
|
|
129
|
-
assert 'GOOGLE_EMAIL=test@gmail.com' in content
|
|
130
|
-
|
|
131
|
-
def test_save_google_credentials_updates_existing_env(self):
|
|
132
|
-
"""Test that saving Google credentials updates existing .env."""
|
|
133
|
-
from connectonion.cli.commands.auth_commands import _save_google_to_env
|
|
134
|
-
|
|
135
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
136
|
-
env_file = Path(tmpdir) / '.env'
|
|
137
|
-
|
|
138
|
-
# Create existing .env with old Google credentials
|
|
139
|
-
env_file.write_text('''OPENONION_API_KEY=existing-key
|
|
140
|
-
GOOGLE_ACCESS_TOKEN=old-token
|
|
141
|
-
GOOGLE_REFRESH_TOKEN=old-refresh
|
|
142
|
-
GOOGLE_EMAIL=old@gmail.com
|
|
143
|
-
OTHER_VAR=keep-this
|
|
144
|
-
''')
|
|
145
|
-
|
|
146
|
-
credentials = {
|
|
147
|
-
'access_token': 'new-token',
|
|
148
|
-
'refresh_token': 'new-refresh',
|
|
149
|
-
'expires_at': '2025-12-31T23:59:59',
|
|
150
|
-
'scopes': 'gmail.send',
|
|
151
|
-
'google_email': 'new@gmail.com'
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
_save_google_to_env(env_file, credentials)
|
|
155
|
-
|
|
156
|
-
content = env_file.read_text()
|
|
157
|
-
|
|
158
|
-
# Should preserve non-Google variables
|
|
159
|
-
assert 'OPENONION_API_KEY=existing-key' in content
|
|
160
|
-
assert 'OTHER_VAR=keep-this' in content
|
|
161
|
-
|
|
162
|
-
# Should update Google credentials
|
|
163
|
-
assert 'GOOGLE_ACCESS_TOKEN=new-token' in content
|
|
164
|
-
assert 'GOOGLE_REFRESH_TOKEN=new-refresh' in content
|
|
165
|
-
assert 'GOOGLE_EMAIL=new@gmail.com' in content
|
|
166
|
-
|
|
167
|
-
# Should not contain old Google credentials
|
|
168
|
-
assert 'old-token' not in content
|
|
169
|
-
assert 'old-refresh' not in content
|
|
170
|
-
|
|
171
|
-
def test_save_google_credentials_file_permissions(self):
|
|
172
|
-
"""Test that .env file has restrictive permissions on Unix."""
|
|
173
|
-
from connectonion.cli.commands.auth_commands import _save_google_to_env
|
|
174
|
-
import sys
|
|
175
|
-
|
|
176
|
-
if sys.platform == 'win32':
|
|
177
|
-
pytest.skip("File permissions test not applicable on Windows")
|
|
178
|
-
|
|
179
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
180
|
-
env_file = Path(tmpdir) / '.env'
|
|
181
|
-
|
|
182
|
-
credentials = {
|
|
183
|
-
'access_token': 'test',
|
|
184
|
-
'refresh_token': 'test',
|
|
185
|
-
'expires_at': '2025-12-31T23:59:59',
|
|
186
|
-
'scopes': 'gmail.send',
|
|
187
|
-
'google_email': 'test@gmail.com'
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
_save_google_to_env(env_file, credentials)
|
|
191
|
-
|
|
192
|
-
# Check file permissions (should be 0o600 = rw-------)
|
|
193
|
-
stat = env_file.stat()
|
|
194
|
-
assert oct(stat.st_mode)[-3:] == '600'
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
class TestAuthGoogleFlow:
|
|
198
|
-
"""Test the co auth google flow with mocked backend."""
|
|
199
|
-
|
|
200
|
-
def setup_method(self):
|
|
201
|
-
"""Setup test environment."""
|
|
202
|
-
self.runner = ArgparseCliRunner()
|
|
203
|
-
|
|
204
|
-
@patch('connectonion.cli.commands.auth_commands.webbrowser')
|
|
205
|
-
@patch('connectonion.cli.commands.auth_commands.requests')
|
|
206
|
-
def test_auth_google_success_flow(self, mock_requests, mock_webbrowser):
|
|
207
|
-
"""Test successful Google OAuth flow."""
|
|
208
|
-
with self.runner.isolated_filesystem():
|
|
209
|
-
# Setup: Create .env with API key
|
|
210
|
-
Path('.env').write_text('OPENONION_API_KEY=test-key\n')
|
|
211
|
-
|
|
212
|
-
# Mock API responses
|
|
213
|
-
mock_revoke_response = Mock()
|
|
214
|
-
mock_revoke_response.status_code = 404 # No existing connection to revoke
|
|
215
|
-
|
|
216
|
-
mock_init_response = Mock()
|
|
217
|
-
mock_init_response.status_code = 200
|
|
218
|
-
mock_init_response.json.return_value = {
|
|
219
|
-
'auth_url': 'https://accounts.google.com/o/oauth2/v2/auth?...'
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
mock_status_response = Mock()
|
|
223
|
-
mock_status_response.status_code = 200
|
|
224
|
-
mock_status_response.json.return_value = {'connected': True}
|
|
225
|
-
|
|
226
|
-
mock_creds_response = Mock()
|
|
227
|
-
mock_creds_response.status_code = 200
|
|
228
|
-
mock_creds_response.json.return_value = {
|
|
229
|
-
'access_token': 'ya29.test',
|
|
230
|
-
'refresh_token': '1//0g.test',
|
|
231
|
-
'expires_at': '2025-12-31T23:59:59',
|
|
232
|
-
'scopes': 'gmail.send,calendar.readonly',
|
|
233
|
-
'google_email': 'test@gmail.com'
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
# Setup mock to return different responses
|
|
237
|
-
mock_requests.delete.return_value = mock_revoke_response # /google/revoke
|
|
238
|
-
mock_requests.get.side_effect = [
|
|
239
|
-
mock_init_response, # /google/init
|
|
240
|
-
mock_status_response, # /google/status
|
|
241
|
-
mock_creds_response # /google/credentials
|
|
242
|
-
]
|
|
243
|
-
|
|
244
|
-
# Mock webbrowser.open to not actually open browser
|
|
245
|
-
mock_webbrowser.open.return_value = True
|
|
246
|
-
|
|
247
|
-
# Mock time.sleep to speed up test
|
|
248
|
-
with patch('time.sleep', return_value=None):
|
|
249
|
-
from connectonion.cli.main import cli
|
|
250
|
-
result = self.runner.invoke(cli, ['auth', 'google'])
|
|
251
|
-
|
|
252
|
-
# Verify revoke was called first (to clear any existing connection)
|
|
253
|
-
mock_requests.delete.assert_called_once()
|
|
254
|
-
|
|
255
|
-
# Verify browser was opened
|
|
256
|
-
mock_webbrowser.open.assert_called_once()
|
|
257
|
-
|
|
258
|
-
# Verify credentials were saved to .env
|
|
259
|
-
env_content = Path('.env').read_text()
|
|
260
|
-
assert 'GOOGLE_ACCESS_TOKEN=ya29.test' in env_content
|
|
261
|
-
assert 'GOOGLE_REFRESH_TOKEN=1//0g.test' in env_content
|
|
262
|
-
assert 'GOOGLE_EMAIL=test@gmail.com' in env_content
|
|
263
|
-
|
|
264
|
-
@patch('connectonion.cli.commands.auth_commands.requests')
|
|
265
|
-
def test_auth_google_init_failure(self, mock_requests):
|
|
266
|
-
"""Test handling of OAuth init failure."""
|
|
267
|
-
with self.runner.isolated_filesystem():
|
|
268
|
-
# Setup: Create .env with API key
|
|
269
|
-
Path('.env').write_text('OPENONION_API_KEY=test-key\n')
|
|
270
|
-
|
|
271
|
-
# Mock revoke (always called first)
|
|
272
|
-
mock_revoke_response = Mock()
|
|
273
|
-
mock_revoke_response.status_code = 404
|
|
274
|
-
mock_requests.delete.return_value = mock_revoke_response
|
|
275
|
-
|
|
276
|
-
# Mock failed init response
|
|
277
|
-
mock_response = Mock()
|
|
278
|
-
mock_response.status_code = 500
|
|
279
|
-
mock_response.text = 'Internal Server Error'
|
|
280
|
-
mock_requests.get.return_value = mock_response
|
|
281
|
-
|
|
282
|
-
from connectonion.cli.main import cli
|
|
283
|
-
result = self.runner.invoke(cli, ['auth', 'google'])
|
|
284
|
-
|
|
285
|
-
# Should show error message
|
|
286
|
-
assert 'Failed to initialize OAuth' in result.output or result.exit_code != 0
|
|
287
|
-
|
|
288
|
-
@patch('connectonion.cli.commands.auth_commands.webbrowser')
|
|
289
|
-
@patch('connectonion.cli.commands.auth_commands.requests')
|
|
290
|
-
@patch('time.sleep')
|
|
291
|
-
def test_auth_google_timeout(self, mock_sleep, mock_requests, mock_webbrowser):
|
|
292
|
-
"""Test handling of authorization timeout."""
|
|
293
|
-
with self.runner.isolated_filesystem():
|
|
294
|
-
# Setup: Create .env with API key
|
|
295
|
-
Path('.env').write_text('OPENONION_API_KEY=test-key\n')
|
|
296
|
-
|
|
297
|
-
# Mock revoke (always called first)
|
|
298
|
-
mock_revoke_response = Mock()
|
|
299
|
-
mock_revoke_response.status_code = 404
|
|
300
|
-
mock_requests.delete.return_value = mock_revoke_response
|
|
301
|
-
|
|
302
|
-
# Mock init response
|
|
303
|
-
mock_init_response = Mock()
|
|
304
|
-
mock_init_response.status_code = 200
|
|
305
|
-
mock_init_response.json.return_value = {
|
|
306
|
-
'auth_url': 'https://accounts.google.com/o/oauth2/v2/auth?...'
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
# Mock status always returns not connected
|
|
310
|
-
mock_status_response = Mock()
|
|
311
|
-
mock_status_response.status_code = 200
|
|
312
|
-
mock_status_response.json.return_value = {'connected': False}
|
|
313
|
-
|
|
314
|
-
mock_requests.get.side_effect = [
|
|
315
|
-
mock_init_response,
|
|
316
|
-
*[mock_status_response] * 60 # Never becomes connected
|
|
317
|
-
]
|
|
318
|
-
|
|
319
|
-
from connectonion.cli.main import cli
|
|
320
|
-
result = self.runner.invoke(cli, ['auth', 'google'])
|
|
321
|
-
|
|
322
|
-
# Should timeout and show error
|
|
323
|
-
assert 'timed out' in result.output.lower() or result.exit_code != 0
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
@pytest.mark.cli
|
|
327
|
-
@pytest.mark.skip(reason="Integration test requires manual OAuth flow")
|
|
328
|
-
class TestAuthGoogleIntegration:
|
|
329
|
-
"""Integration tests for co auth google (requires installation)."""
|
|
330
|
-
|
|
331
|
-
def test_auth_google_command_exists(self):
|
|
332
|
-
"""Test that 'co auth google' command is recognized."""
|
|
333
|
-
import subprocess
|
|
334
|
-
|
|
335
|
-
# Just check that the command is recognized (won't work without API key)
|
|
336
|
-
result = subprocess.run(
|
|
337
|
-
['co', 'auth', '--help'],
|
|
338
|
-
capture_output=True,
|
|
339
|
-
text=True,
|
|
340
|
-
timeout=5
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
# Command should show google as an option
|
|
344
|
-
assert 'google' in result.stdout.lower() or 'google' in result.stderr.lower()
|