pygeai 0.6.0b14__py3-none-any.whl → 0.6.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.
Files changed (216) hide show
  1. pygeai/__init__.py +1 -2
  2. pygeai/_docs/source/content/api_reference/project.rst +392 -0
  3. pygeai/_docs/source/content/authentication.rst +130 -1
  4. pygeai/_docs/source/content/debugger.rst +327 -157
  5. pygeai/_docs/source/pygeai.core.common.rst +8 -0
  6. pygeai/_docs/source/pygeai.tests.auth.rst +56 -0
  7. pygeai/_docs/source/pygeai.tests.cli.rst +8 -0
  8. pygeai/admin/clients.py +1 -3
  9. pygeai/analytics/clients.py +1 -1
  10. pygeai/assistant/clients.py +2 -7
  11. pygeai/assistant/data/clients.py +0 -8
  12. pygeai/assistant/data_analyst/clients.py +0 -2
  13. pygeai/assistant/managers.py +1 -1
  14. pygeai/assistant/rag/clients.py +0 -2
  15. pygeai/assistant/rag/mappers.py +9 -11
  16. pygeai/auth/clients.py +26 -7
  17. pygeai/auth/endpoints.py +2 -1
  18. pygeai/chat/clients.py +2 -2
  19. pygeai/chat/managers.py +1 -1
  20. pygeai/cli/commands/admin.py +13 -25
  21. pygeai/cli/commands/analytics.py +31 -71
  22. pygeai/cli/commands/assistant.py +84 -138
  23. pygeai/cli/commands/auth.py +23 -46
  24. pygeai/cli/commands/base.py +0 -1
  25. pygeai/cli/commands/chat.py +218 -209
  26. pygeai/cli/commands/common.py +5 -5
  27. pygeai/cli/commands/configuration.py +79 -29
  28. pygeai/cli/commands/docs.py +3 -4
  29. pygeai/cli/commands/embeddings.py +13 -19
  30. pygeai/cli/commands/evaluation.py +133 -344
  31. pygeai/cli/commands/feedback.py +7 -15
  32. pygeai/cli/commands/files.py +26 -53
  33. pygeai/cli/commands/gam.py +28 -69
  34. pygeai/cli/commands/lab/ai_lab.py +96 -142
  35. pygeai/cli/commands/lab/common.py +1 -1
  36. pygeai/cli/commands/lab/spec.py +12 -32
  37. pygeai/cli/commands/llm.py +9 -18
  38. pygeai/cli/commands/migrate.py +43 -99
  39. pygeai/cli/commands/organization.py +223 -196
  40. pygeai/cli/commands/rag.py +35 -58
  41. pygeai/cli/commands/rerank.py +21 -25
  42. pygeai/cli/commands/secrets.py +39 -67
  43. pygeai/cli/commands/usage_limits.py +50 -136
  44. pygeai/cli/commands/validators.py +1 -1
  45. pygeai/cli/geai.py +32 -3
  46. pygeai/cli/geai_proxy.py +6 -2
  47. pygeai/cli/install_man.py +1 -1
  48. pygeai/cli/parsers.py +1 -1
  49. pygeai/core/base/clients.py +90 -21
  50. pygeai/core/base/mappers.py +39 -55
  51. pygeai/core/base/session.py +129 -18
  52. pygeai/core/common/config.py +50 -13
  53. pygeai/core/common/constants.py +8 -0
  54. pygeai/core/common/exceptions.py +6 -0
  55. pygeai/core/embeddings/clients.py +0 -1
  56. pygeai/core/embeddings/managers.py +0 -1
  57. pygeai/core/feedback/clients.py +0 -2
  58. pygeai/core/feedback/models.py +1 -1
  59. pygeai/core/files/clients.py +0 -3
  60. pygeai/core/files/managers.py +1 -1
  61. pygeai/core/files/mappers.py +4 -5
  62. pygeai/core/llm/clients.py +0 -1
  63. pygeai/core/models.py +4 -4
  64. pygeai/core/plugins/clients.py +0 -3
  65. pygeai/core/plugins/models.py +2 -2
  66. pygeai/core/rerank/clients.py +0 -2
  67. pygeai/core/secrets/clients.py +0 -2
  68. pygeai/core/services/rest.py +80 -14
  69. pygeai/core/singleton.py +24 -0
  70. pygeai/dbg/__init__.py +2 -2
  71. pygeai/dbg/debugger.py +276 -38
  72. pygeai/evaluation/clients.py +2 -4
  73. pygeai/evaluation/dataset/clients.py +0 -1
  74. pygeai/evaluation/plan/clients.py +0 -2
  75. pygeai/evaluation/result/clients.py +0 -2
  76. pygeai/gam/clients.py +1 -3
  77. pygeai/health/clients.py +1 -3
  78. pygeai/lab/clients.py +0 -1
  79. pygeai/lab/managers.py +0 -1
  80. pygeai/lab/models.py +0 -1
  81. pygeai/lab/strategies/clients.py +1 -2
  82. pygeai/lab/tools/clients.py +2 -2
  83. pygeai/lab/tools/mappers.py +1 -1
  84. pygeai/migration/strategies.py +5 -6
  85. pygeai/migration/tools.py +1 -1
  86. pygeai/organization/clients.py +118 -12
  87. pygeai/organization/endpoints.py +1 -0
  88. pygeai/organization/limits/clients.py +4 -6
  89. pygeai/organization/limits/managers.py +1 -4
  90. pygeai/organization/managers.py +2 -2
  91. pygeai/proxy/config.py +1 -0
  92. pygeai/proxy/managers.py +6 -5
  93. pygeai/tests/admin/test_clients.py +11 -11
  94. pygeai/tests/assistants/rag/test_clients.py +1 -1
  95. pygeai/tests/assistants/rag/test_models.py +1 -2
  96. pygeai/tests/assistants/test_clients.py +1 -1
  97. pygeai/tests/assistants/test_managers.py +1 -3
  98. pygeai/tests/auth/test_cli_configuration.py +252 -0
  99. pygeai/tests/auth/test_client_initialization.py +411 -0
  100. pygeai/tests/auth/test_clients.py +29 -27
  101. pygeai/tests/auth/test_config_manager.py +305 -0
  102. pygeai/tests/auth/test_header_injection.py +294 -0
  103. pygeai/tests/auth/test_oauth.py +3 -1
  104. pygeai/tests/auth/test_session_logging.py +119 -0
  105. pygeai/tests/auth/test_session_validation.py +408 -0
  106. pygeai/tests/auth/test_singleton_reset.py +201 -0
  107. pygeai/tests/chat/test_clients.py +1 -1
  108. pygeai/tests/chat/test_iris.py +1 -1
  109. pygeai/tests/chat/test_ui.py +0 -2
  110. pygeai/tests/cli/commands/lab/test_ai_lab.py +1 -3
  111. pygeai/tests/cli/commands/lab/test_common.py +0 -1
  112. pygeai/tests/cli/commands/test_chat.py +1 -1
  113. pygeai/tests/cli/commands/test_common.py +0 -1
  114. pygeai/tests/cli/commands/test_embeddings.py +2 -2
  115. pygeai/tests/cli/commands/test_evaluation.py +1 -9
  116. pygeai/tests/cli/commands/test_llm.py +1 -1
  117. pygeai/tests/cli/commands/test_migrate.py +1 -1
  118. pygeai/tests/cli/commands/test_rerank.py +0 -1
  119. pygeai/tests/cli/commands/test_secrets.py +1 -1
  120. pygeai/tests/cli/commands/test_show_help.py +0 -1
  121. pygeai/tests/cli/commands/test_validators.py +0 -1
  122. pygeai/tests/cli/test_credentials_flag.py +312 -0
  123. pygeai/tests/cli/test_error_handler.py +0 -1
  124. pygeai/tests/core/base/test_mappers.py +2 -2
  125. pygeai/tests/core/base/test_models.py +4 -4
  126. pygeai/tests/core/common/test_config.py +2 -7
  127. pygeai/tests/core/common/test_decorators.py +0 -1
  128. pygeai/tests/core/embeddings/test_managers.py +1 -1
  129. pygeai/tests/core/feedback/test_clients.py +2 -2
  130. pygeai/tests/core/files/test_clients.py +6 -6
  131. pygeai/tests/core/files/test_models.py +0 -1
  132. pygeai/tests/core/files/test_responses.py +0 -1
  133. pygeai/tests/core/llm/test_clients.py +1 -1
  134. pygeai/tests/core/plugins/test_clients.py +4 -4
  135. pygeai/tests/core/rerank/test_mappers.py +1 -3
  136. pygeai/tests/core/secrets/test_clients.py +2 -3
  137. pygeai/tests/core/services/test_rest.py +10 -10
  138. pygeai/tests/core/utils/test_console.py +0 -1
  139. pygeai/tests/dbg/test_debugger.py +95 -8
  140. pygeai/tests/evaluation/dataset/test_clients.py +24 -27
  141. pygeai/tests/evaluation/plan/test_clients.py +16 -18
  142. pygeai/tests/evaluation/result/test_clients.py +4 -5
  143. pygeai/tests/health/test_clients.py +2 -2
  144. pygeai/tests/integration/lab/agents/test_create_agent.py +1 -3
  145. pygeai/tests/integration/lab/agents/test_get_agent.py +1 -1
  146. pygeai/tests/integration/lab/processes/test_create_process.py +2 -2
  147. pygeai/tests/integration/lab/processes/test_create_task.py +2 -3
  148. pygeai/tests/integration/lab/processes/test_delete_process.py +0 -1
  149. pygeai/tests/integration/lab/processes/test_get_process.py +2 -4
  150. pygeai/tests/integration/lab/processes/test_list_process_instances.py +1 -3
  151. pygeai/tests/integration/lab/processes/test_update_process.py +3 -9
  152. pygeai/tests/integration/lab/reasoning_strategies/test_update_reasoning_strategy.py +1 -2
  153. pygeai/tests/integration/lab/tools/test_delete_tool.py +1 -1
  154. pygeai/tests/integration/lab/tools/test_list_tools.py +1 -1
  155. pygeai/tests/integration/lab/tools/test_update_tool.py +1 -1
  156. pygeai/tests/lab/agents/test_clients.py +17 -17
  157. pygeai/tests/lab/processes/test_clients.py +67 -67
  158. pygeai/tests/lab/processes/test_mappers.py +23 -23
  159. pygeai/tests/lab/spec/test_loader.py +0 -2
  160. pygeai/tests/lab/spec/test_parsers.py +1 -2
  161. pygeai/tests/lab/strategies/test_clients.py +10 -10
  162. pygeai/tests/lab/test_managers.py +3 -5
  163. pygeai/tests/lab/test_mappers.py +1 -4
  164. pygeai/tests/lab/tools/test_clients.py +21 -21
  165. pygeai/tests/lab/tools/test_mappers.py +0 -1
  166. pygeai/tests/organization/limits/test_clients.py +33 -33
  167. pygeai/tests/organization/limits/test_managers.py +7 -7
  168. pygeai/tests/organization/test_clients.py +78 -60
  169. pygeai/tests/proxy/test_clients.py +1 -1
  170. pygeai/tests/proxy/test_integration.py +1 -4
  171. pygeai/tests/proxy/test_managers.py +1 -2
  172. pygeai/tests/proxy/test_servers.py +1 -2
  173. pygeai/tests/snippets/assistants/rag/delete_rag_assistant.py +0 -1
  174. pygeai/tests/snippets/assistants/rag/get_documents.py +0 -1
  175. pygeai/tests/snippets/assistants/rag/get_rag_assistant_data.py +0 -1
  176. pygeai/tests/snippets/chat/get_request_status.py +0 -1
  177. pygeai/tests/snippets/dbg/file_debugging.py +72 -0
  178. pygeai/tests/snippets/dbg/module_debugging.py +60 -0
  179. pygeai/tests/snippets/embeddings/cohere_example.py +2 -2
  180. pygeai/tests/snippets/embeddings/openai_base64_example.py +1 -1
  181. pygeai/tests/snippets/evaluation/dataset/complete_workflow_example.py +8 -8
  182. pygeai/tests/snippets/evaluation/plan/complete_workflow_example.py +5 -5
  183. pygeai/tests/snippets/evaluation/result/complete_workflow_example.py +3 -3
  184. pygeai/tests/snippets/lab/agentic_flow_example_1.py +1 -1
  185. pygeai/tests/snippets/lab/agentic_flow_example_2.py +3 -4
  186. pygeai/tests/snippets/lab/agents/create_agent_with_permissions.py +2 -2
  187. pygeai/tests/snippets/lab/agents/delete_agent.py +1 -2
  188. pygeai/tests/snippets/lab/agents/get_agent.py +1 -1
  189. pygeai/tests/snippets/lab/agents/get_agent_with_new_fields.py +10 -10
  190. pygeai/tests/snippets/lab/agents/get_sharing_link.py +0 -1
  191. pygeai/tests/snippets/lab/agents/list_agents.py +1 -1
  192. pygeai/tests/snippets/lab/agents/publish_agent_revision.py +0 -1
  193. pygeai/tests/snippets/lab/agents/update_agent_properties.py +1 -1
  194. pygeai/tests/snippets/lab/crud_ui.py +3 -5
  195. pygeai/tests/snippets/lab/processes/kbs/get_kb.py +0 -1
  196. pygeai/tests/snippets/lab/processes/kbs/list_kbs.py +0 -1
  197. pygeai/tests/snippets/lab/processes/list_processes.py +1 -1
  198. pygeai/tests/snippets/lab/samples/summarize_files.py +0 -3
  199. pygeai/tests/snippets/lab/strategies/get_reasoning_strategy.py +0 -1
  200. pygeai/tests/snippets/lab/strategies/list_reasoning_strategies.py +1 -1
  201. pygeai/tests/snippets/lab/tools/get_tool.py +1 -1
  202. pygeai/tests/snippets/lab/tools/publish_tool_revision.py +0 -1
  203. pygeai/tests/snippets/lab/tools/set_parameters.py +1 -2
  204. pygeai/tests/snippets/lab/use_cases/c_code_fixer_agent_flow.py +2 -3
  205. pygeai/tests/snippets/lab/use_cases/file_summarizer_example_2.py +1 -1
  206. pygeai/tests/snippets/lab/use_cases/update_cli_expert.py +0 -1
  207. pygeai/tests/snippets/lab/use_cases/update_lab_expert.py +0 -1
  208. pygeai/tests/snippets/lab/use_cases/update_web_designer.py +0 -1
  209. pygeai/tests/snippets/lab/use_cases/update_web_reader.py +0 -1
  210. pygeai/tests/snippets/migrate/orchestrator_examples.py +1 -1
  211. {pygeai-0.6.0b14.dist-info → pygeai-0.6.1.dist-info}/METADATA +32 -7
  212. {pygeai-0.6.0b14.dist-info → pygeai-0.6.1.dist-info}/RECORD +216 -205
  213. {pygeai-0.6.0b14.dist-info → pygeai-0.6.1.dist-info}/WHEEL +0 -0
  214. {pygeai-0.6.0b14.dist-info → pygeai-0.6.1.dist-info}/entry_points.txt +0 -0
  215. {pygeai-0.6.0b14.dist-info → pygeai-0.6.1.dist-info}/licenses/LICENSE +0 -0
  216. {pygeai-0.6.0b14.dist-info → pygeai-0.6.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,252 @@
1
+ import unittest
2
+ import tempfile
3
+ import os
4
+ from io import StringIO
5
+ from unittest.mock import patch
6
+
7
+ from pygeai.cli.commands.configuration import configure
8
+ from pygeai.cli.commands import Option
9
+ from pygeai.core.common.config import reset_settings, get_settings
10
+
11
+
12
+ class TestCLIConfigurationWarnings(unittest.TestCase):
13
+ """
14
+ Tests for CLI configuration warnings and validation.
15
+
16
+ python -m unittest pygeai.tests.auth.test_cli_configuration.TestCLIConfigurationWarnings
17
+ """
18
+
19
+ def setUp(self):
20
+ """Set up test fixtures"""
21
+ reset_settings()
22
+ self.temp_creds_file = tempfile.NamedTemporaryFile(mode='w', suffix='_credentials', delete=False)
23
+ self.temp_creds_file.close()
24
+
25
+ def tearDown(self):
26
+ """Clean up test fixtures"""
27
+ if os.path.exists(self.temp_creds_file.name):
28
+ os.unlink(self.temp_creds_file.name)
29
+ reset_settings()
30
+
31
+ @patch('sys.stdout', new_callable=StringIO)
32
+ def test_warns_about_mixed_auth_in_same_profile(self, mock_stdout):
33
+ """Test that CLI warns when both API key and OAuth are set for same profile"""
34
+ reset_settings()
35
+ get_settings(credentials_file=self.temp_creds_file.name)
36
+
37
+ option_list = [
38
+ (Option("api_key", ["--key"], "Set API key", True), "test_api_key"),
39
+ (Option("access_token", ["--access-token"], "Set access token", True), "test_oauth_token"),
40
+ (Option("profile_alias", ["--profile-alias"], "Set alias", True), "test")
41
+ ]
42
+
43
+ configure(option_list)
44
+
45
+ output = mock_stdout.getvalue()
46
+
47
+ self.assertIn("WARNING", output)
48
+ self.assertIn("2 different types of authentication", output)
49
+
50
+ @patch('sys.stdout', new_callable=StringIO)
51
+ def test_no_warning_for_single_auth_type(self, mock_stdout):
52
+ """Test that no warning is issued when only one auth type is set"""
53
+ reset_settings()
54
+ get_settings(credentials_file=self.temp_creds_file.name)
55
+
56
+ option_list = [
57
+ (Option("api_key", ["--key"], "Set API key", True), "test_api_key"),
58
+ (Option("base_url", ["--url"], "Set base URL", True), "https://test.example.com"),
59
+ (Option("profile_alias", ["--profile-alias"], "Set alias", True), "test")
60
+ ]
61
+
62
+ configure(option_list)
63
+
64
+ output = mock_stdout.getvalue()
65
+
66
+ self.assertNotIn("WARNING", output)
67
+ self.assertNotIn("2 different types of authentication", output)
68
+
69
+ @patch('sys.stdout', new_callable=StringIO)
70
+ def test_api_key_configuration_success_message(self, mock_stdout):
71
+ """Test that API key configuration shows success message"""
72
+ reset_settings()
73
+ get_settings(credentials_file=self.temp_creds_file.name)
74
+
75
+ option_list = [
76
+ (Option("api_key", ["--key"], "Set API key", True), "test_api_key"),
77
+ (Option("profile_alias", ["--profile-alias"], "Set alias", True), "test")
78
+ ]
79
+
80
+ configure(option_list)
81
+
82
+ output = mock_stdout.getvalue()
83
+
84
+ self.assertIn("GEAI API KEY for alias 'test' saved successfully!", output)
85
+
86
+ @patch('sys.stdout', new_callable=StringIO)
87
+ def test_base_url_configuration_success_message(self, mock_stdout):
88
+ """Test that base URL configuration shows success message"""
89
+ reset_settings()
90
+ get_settings(credentials_file=self.temp_creds_file.name)
91
+
92
+ option_list = [
93
+ (Option("base_url", ["--url"], "Set base URL", True), "https://test.example.com"),
94
+ (Option("profile_alias", ["--profile-alias"], "Set alias", True), "test")
95
+ ]
96
+
97
+ configure(option_list)
98
+
99
+ output = mock_stdout.getvalue()
100
+
101
+ self.assertIn("GEAI API BASE URL for alias 'test' saved successfully!", output)
102
+
103
+ @patch('sys.stdout', new_callable=StringIO)
104
+ def test_oauth_configuration_success_messages(self, mock_stdout):
105
+ """Test that OAuth configuration shows success messages"""
106
+ reset_settings()
107
+ get_settings(credentials_file=self.temp_creds_file.name)
108
+
109
+ option_list = [
110
+ (Option("access_token", ["--access-token"], "Set access token", True), "oauth_token_123"),
111
+ (Option("project_id", ["--project-id"], "Set project ID", True), "project-456"),
112
+ (Option("profile_alias", ["--profile-alias"], "Set alias", True), "oauth_profile")
113
+ ]
114
+
115
+ configure(option_list)
116
+
117
+ output = mock_stdout.getvalue()
118
+
119
+ self.assertIn("GEAI OAUTH2 ACCESS TOKEN for alias 'oauth_profile' saved successfully!", output)
120
+ self.assertIn("GEAI PROJECT ID for alias 'oauth_profile' saved successfully!", output)
121
+
122
+ @patch('sys.stdout', new_callable=StringIO)
123
+ def test_organization_id_configuration_success_message(self, mock_stdout):
124
+ """Test that organization ID configuration shows success message"""
125
+ reset_settings()
126
+ get_settings(credentials_file=self.temp_creds_file.name)
127
+
128
+ option_list = [
129
+ (Option("organization_id", ["--organization-id"], "Set org ID", True), "org-789"),
130
+ (Option("profile_alias", ["--profile-alias"], "Set alias", True), "test")
131
+ ]
132
+
133
+ configure(option_list)
134
+
135
+ output = mock_stdout.getvalue()
136
+
137
+ self.assertIn("GEAI ORGANIZATION ID for alias 'test' saved successfully!", output)
138
+
139
+ @patch('sys.stdout', new_callable=StringIO)
140
+ def test_eval_url_configuration_success_message(self, mock_stdout):
141
+ """Test that eval URL configuration shows success message"""
142
+ reset_settings()
143
+ get_settings(credentials_file=self.temp_creds_file.name)
144
+
145
+ option_list = [
146
+ (Option("eval_url", ["--eval-url"], "Set eval URL", True), "https://eval.test.example.com"),
147
+ (Option("profile_alias", ["--profile-alias"], "Set alias", True), "test")
148
+ ]
149
+
150
+ configure(option_list)
151
+
152
+ output = mock_stdout.getvalue()
153
+
154
+ self.assertIn("GEAI API EVAL URL for alias 'test' saved successfully!", output)
155
+
156
+ @patch('sys.stdout', new_callable=StringIO)
157
+ def test_list_aliases_displays_configured_profiles(self, mock_stdout):
158
+ """Test that --list flag displays all configured profiles"""
159
+ reset_settings()
160
+ settings = get_settings(credentials_file=self.temp_creds_file.name)
161
+
162
+ settings.set_base_url("https://dev.example.com", alias="dev")
163
+ settings.set_base_url("https://prod.example.com", alias="prod")
164
+
165
+ option_list = [
166
+ (Option("list", ["--list"], "List aliases", False), None)
167
+ ]
168
+
169
+ configure(option_list)
170
+
171
+ output = mock_stdout.getvalue()
172
+
173
+ self.assertIn("Alias: dev -> Base URL: https://dev.example.com", output)
174
+ self.assertIn("Alias: prod -> Base URL: https://prod.example.com", output)
175
+
176
+ @patch('builtins.input', return_value='n')
177
+ @patch('sys.stdout', new_callable=StringIO)
178
+ def test_remove_alias_cancellation(self, mock_stdout, mock_input):
179
+ """Test that alias removal can be cancelled"""
180
+ reset_settings()
181
+ settings = get_settings(credentials_file=self.temp_creds_file.name)
182
+ settings.set_api_key("test_key", alias="test")
183
+ settings.set_base_url("https://test.example.com", alias="test")
184
+
185
+ option_list = [
186
+ (Option("remove_alias", ["--remove-alias"], "Remove alias", True), "test")
187
+ ]
188
+
189
+ configure(option_list)
190
+
191
+ output = mock_stdout.getvalue()
192
+
193
+ self.assertIn("kept in configuration file", output)
194
+ self.assertIn("test", settings.list_aliases())
195
+
196
+ @patch('builtins.input', return_value='y')
197
+ @patch('sys.stdout', new_callable=StringIO)
198
+ def test_remove_alias_confirmation(self, mock_stdout, mock_input):
199
+ """Test that alias can be removed with confirmation"""
200
+ reset_settings()
201
+ settings = get_settings(credentials_file=self.temp_creds_file.name)
202
+ settings.set_api_key("test_key", alias="test")
203
+ settings.set_base_url("https://test.example.com", alias="test")
204
+
205
+ option_list = [
206
+ (Option("remove_alias", ["--remove-alias"], "Remove alias", True), "test")
207
+ ]
208
+
209
+ configure(option_list)
210
+
211
+ output = mock_stdout.getvalue()
212
+
213
+ self.assertIn("removed from configuration file", output)
214
+ self.assertNotIn("test", settings.list_aliases())
215
+
216
+ def test_configuration_persists_to_file(self):
217
+ """Test that configuration is persisted to credentials file"""
218
+ reset_settings()
219
+ get_settings(credentials_file=self.temp_creds_file.name)
220
+
221
+ option_list = [
222
+ (Option("api_key", ["--key"], "Set API key", True), "persisted_key"),
223
+ (Option("base_url", ["--url"], "Set base URL", True), "https://persisted.example.com"),
224
+ (Option("profile_alias", ["--profile-alias"], "Set alias", True), "persist")
225
+ ]
226
+
227
+ configure(option_list)
228
+
229
+ reset_settings()
230
+ new_settings = get_settings(credentials_file=self.temp_creds_file.name)
231
+
232
+ self.assertEqual(new_settings.get_api_key(alias="persist"), "persisted_key")
233
+ self.assertEqual(new_settings.get_base_url(alias="persist"), "https://persisted.example.com")
234
+
235
+ def test_default_alias_used_when_not_specified(self):
236
+ """Test that 'default' alias is used when profile_alias is not specified"""
237
+ reset_settings()
238
+ get_settings(credentials_file=self.temp_creds_file.name)
239
+
240
+ option_list = [
241
+ (Option("api_key", ["--key"], "Set API key", True), "default_key"),
242
+ ]
243
+
244
+ configure(option_list)
245
+
246
+ settings = get_settings()
247
+
248
+ self.assertEqual(settings.get_api_key(alias="default"), "default_key")
249
+
250
+
251
+ if __name__ == '__main__':
252
+ unittest.main()
@@ -0,0 +1,411 @@
1
+ import unittest
2
+ import tempfile
3
+ import os
4
+ import logging
5
+ import io
6
+
7
+ from pygeai.core.base.clients import BaseClient
8
+ from pygeai.core.base.session import reset_session, get_session
9
+ from pygeai.core.common.config import reset_settings, get_settings
10
+ from pygeai.core.common.constants import AuthType
11
+ from pygeai.core.common.exceptions import MissingRequirementException, MixedAuthenticationException
12
+
13
+
14
+ class TestBaseClientInitialization(unittest.TestCase):
15
+ """
16
+ Tests for BaseClient initialization with different authentication methods.
17
+
18
+ python -m unittest pygeai.tests.auth.test_client_initialization.TestBaseClientInitialization
19
+ """
20
+
21
+ def setUp(self):
22
+ """Set up test fixtures"""
23
+ reset_session()
24
+ reset_settings()
25
+
26
+ from pygeai.core.base.clients import BaseClient
27
+ BaseClient._logged_session_config = None
28
+
29
+ def tearDown(self):
30
+ """Clean up test fixtures"""
31
+ from pygeai.core.base.clients import BaseClient
32
+ BaseClient._logged_session_config = None
33
+
34
+ reset_session()
35
+ reset_settings()
36
+
37
+ def test_init_with_api_key_direct(self):
38
+ """Test BaseClient initialization with direct API key"""
39
+ client = BaseClient(
40
+ api_key="test_api_key",
41
+ base_url="https://api.test.com"
42
+ )
43
+
44
+ self.assertEqual(client.session.api_key, "test_api_key")
45
+ self.assertEqual(client.session.base_url, "https://api.test.com")
46
+ self.assertEqual(client.session.auth_type, AuthType.API_KEY)
47
+ self.assertEqual(client.api_service.token, "test_api_key")
48
+
49
+ def test_init_with_oauth_direct(self):
50
+ """Test BaseClient initialization with direct OAuth credentials"""
51
+ client = BaseClient(
52
+ base_url="https://api.test.com",
53
+ access_token="oauth_token_123",
54
+ project_id="project-456"
55
+ )
56
+
57
+ self.assertEqual(client.session.access_token, "oauth_token_123")
58
+ self.assertEqual(client.session.project_id, "project-456")
59
+ self.assertEqual(client.session.auth_type, AuthType.OAUTH_TOKEN)
60
+ self.assertEqual(client.api_service.token, "oauth_token_123")
61
+ self.assertEqual(client.api_service.project_id, "project-456")
62
+
63
+ def test_init_with_alias_loads_from_config(self):
64
+ """Test BaseClient initialization with alias loads from config"""
65
+ with tempfile.NamedTemporaryFile(mode='w', suffix='_credentials', delete=False) as f:
66
+ creds_file = f.name
67
+ f.write('[staging]\n')
68
+ f.write('GEAI_API_KEY = staging_api_key\n')
69
+ f.write('GEAI_API_BASE_URL = https://staging.example.com\n')
70
+
71
+ try:
72
+ reset_settings()
73
+ get_settings(credentials_file=creds_file)
74
+ reset_session()
75
+
76
+ client = BaseClient(alias="staging")
77
+
78
+ self.assertEqual(client.session.api_key, "staging_api_key")
79
+ self.assertEqual(client.session.base_url, "https://staging.example.com")
80
+ finally:
81
+ os.unlink(creds_file)
82
+
83
+ def test_init_with_nonexistent_alias_raises_error(self):
84
+ """Test that using non-existent alias raises MissingRequirementException"""
85
+ with tempfile.NamedTemporaryFile(mode='w', suffix='_credentials', delete=False) as f:
86
+ creds_file = f.name
87
+ f.write('[default]\n')
88
+ f.write('GEAI_API_KEY = default_key\n')
89
+ f.write('GEAI_API_BASE_URL = https://default.example.com\n')
90
+
91
+ try:
92
+ reset_settings()
93
+ get_settings(credentials_file=creds_file)
94
+ reset_session()
95
+
96
+ with self.assertRaises(MissingRequirementException):
97
+ BaseClient(alias="nonexistent")
98
+ finally:
99
+ os.unlink(creds_file)
100
+
101
+ def test_init_oauth_without_project_id_raises_error(self):
102
+ """Test that OAuth without project_id raises MissingRequirementException"""
103
+ with self.assertRaises(MissingRequirementException) as context:
104
+ BaseClient(
105
+ base_url="https://api.test.com",
106
+ access_token="oauth_token_123"
107
+ )
108
+
109
+ self.assertIn("project_id is required", str(context.exception))
110
+
111
+ def test_init_mixed_auth_without_allow_raises_error(self):
112
+ """Test that mixed auth without allow_mixed_auth raises MixedAuthenticationException"""
113
+ with self.assertRaises(MixedAuthenticationException) as context:
114
+ BaseClient(
115
+ api_key="api_key_123",
116
+ base_url="https://api.test.com",
117
+ access_token="oauth_token_123",
118
+ project_id="project-456",
119
+ allow_mixed_auth=False
120
+ )
121
+
122
+ self.assertIn("Cannot specify both", str(context.exception))
123
+
124
+ def test_init_mixed_auth_with_allow_succeeds(self):
125
+ """Test that mixed auth with allow_mixed_auth=True succeeds"""
126
+ client = BaseClient(
127
+ api_key="api_key_123",
128
+ base_url="https://api.test.com",
129
+ access_token="oauth_token_123",
130
+ project_id="project-456",
131
+ allow_mixed_auth=True
132
+ )
133
+
134
+ self.assertEqual(client.session.auth_type, AuthType.OAUTH_TOKEN)
135
+ self.assertEqual(client.api_service.token, "oauth_token_123")
136
+
137
+ def test_init_with_organization_id(self):
138
+ """Test BaseClient initialization with organization_id"""
139
+ client = BaseClient(
140
+ base_url="https://api.test.com",
141
+ access_token="oauth_token_123",
142
+ project_id="project-456",
143
+ organization_id="org-789"
144
+ )
145
+
146
+ self.assertEqual(client.session.organization_id, "org-789")
147
+ self.assertEqual(client.api_service.organization_id, "org-789")
148
+
149
+ def test_init_mutates_singleton_session(self):
150
+ """Test that direct credential initialization mutates the singleton session"""
151
+ with tempfile.NamedTemporaryFile(mode='w', suffix='_credentials', delete=False) as f:
152
+ creds_file = f.name
153
+ f.write('[default]\n')
154
+ f.write('GEAI_API_KEY = config_key\n')
155
+ f.write('GEAI_API_BASE_URL = https://config.example.com\n')
156
+
157
+ try:
158
+ reset_settings()
159
+ get_settings(credentials_file=creds_file)
160
+ reset_session()
161
+
162
+ session_before = get_session()
163
+ self.assertEqual(session_before.api_key, "config_key")
164
+
165
+ client = BaseClient(
166
+ api_key="new_key",
167
+ base_url="https://new.example.com"
168
+ )
169
+
170
+ session_after = get_session()
171
+ self.assertEqual(session_after.api_key, "new_key")
172
+ self.assertIs(session_before, session_after)
173
+ finally:
174
+ os.unlink(creds_file)
175
+
176
+ def test_init_default_uses_default_session(self):
177
+ """Test that initialization without params uses default session"""
178
+ with tempfile.NamedTemporaryFile(mode='w', suffix='_credentials', delete=False) as f:
179
+ creds_file = f.name
180
+ f.write('[default]\n')
181
+ f.write('GEAI_API_KEY = default_key\n')
182
+ f.write('GEAI_API_BASE_URL = https://default.example.com\n')
183
+
184
+ try:
185
+ reset_settings()
186
+ get_settings(credentials_file=creds_file)
187
+ reset_session()
188
+
189
+ client = BaseClient()
190
+
191
+ self.assertEqual(client.session.api_key, "default_key")
192
+ self.assertEqual(client.session.base_url, "https://default.example.com")
193
+ finally:
194
+ os.unlink(creds_file)
195
+
196
+
197
+ class TestBaseClientAuthenticationLogging(unittest.TestCase):
198
+ """
199
+ Tests for authentication logging behavior in BaseClient.
200
+
201
+ python -m unittest pygeai.tests.auth.test_client_initialization.TestBaseClientAuthenticationLogging
202
+ """
203
+
204
+ def setUp(self):
205
+ """Set up test fixtures"""
206
+ reset_session()
207
+ reset_settings()
208
+
209
+ from pygeai.core.base.clients import BaseClient
210
+ BaseClient._logged_session_config = None
211
+
212
+ self.log_capture = io.StringIO()
213
+ self.handler = logging.StreamHandler(self.log_capture)
214
+ self.handler.setLevel(logging.INFO)
215
+
216
+ self.geai_logger = logging.getLogger('geai')
217
+ self._original_handlers = self.geai_logger.handlers.copy()
218
+ self._original_level = self.geai_logger.level
219
+
220
+ self.geai_logger.handlers = []
221
+ self.geai_logger.addHandler(self.handler)
222
+ self.geai_logger.setLevel(logging.INFO)
223
+ self.geai_logger.propagate = False
224
+
225
+ def tearDown(self):
226
+ """Clean up test fixtures"""
227
+ self.geai_logger.handlers = self._original_handlers
228
+ self.geai_logger.level = self._original_level
229
+
230
+ from pygeai.core.base.clients import BaseClient
231
+ BaseClient._logged_session_config = None
232
+
233
+ reset_session()
234
+ reset_settings()
235
+
236
+ def test_logs_api_key_authentication(self):
237
+ """Test that API key authentication is logged"""
238
+ BaseClient(
239
+ api_key="test_key",
240
+ base_url="https://api.test.com"
241
+ )
242
+
243
+ log_output = self.log_capture.getvalue()
244
+
245
+ self.assertIn("Using API Key authentication", log_output)
246
+
247
+ def test_logs_oauth_authentication(self):
248
+ """Test that OAuth authentication is logged"""
249
+ BaseClient(
250
+ base_url="https://api.test.com",
251
+ access_token="oauth_token",
252
+ project_id="project-123"
253
+ )
254
+
255
+ log_output = self.log_capture.getvalue()
256
+
257
+ self.assertIn("Using OAuth 2.0 authentication", log_output)
258
+ self.assertIn("Project ID: project-123", log_output)
259
+
260
+ def test_logs_organization_id_when_present(self):
261
+ """Test that organization ID is logged when present"""
262
+ BaseClient(
263
+ base_url="https://api.test.com",
264
+ access_token="oauth_token",
265
+ project_id="project-123",
266
+ organization_id="org-456"
267
+ )
268
+
269
+ log_output = self.log_capture.getvalue()
270
+
271
+ self.assertIn("Organization ID: org-456", log_output)
272
+
273
+ def test_logs_base_url_and_alias(self):
274
+ """Test that base URL and alias are logged"""
275
+ BaseClient(
276
+ api_key="test_key",
277
+ base_url="https://api.test.com"
278
+ )
279
+
280
+ log_output = self.log_capture.getvalue()
281
+
282
+ self.assertIn("Base URL: https://api.test.com", log_output)
283
+ self.assertIn("Alias: default", log_output)
284
+
285
+ def test_authentication_logged_only_once_for_same_config(self):
286
+ """Test that authentication is logged only once for the same configuration"""
287
+ BaseClient(
288
+ api_key="test_key",
289
+ base_url="https://api.test.com"
290
+ )
291
+
292
+ first_log = self.log_capture.getvalue()
293
+ auth_count_first = first_log.count("Using API Key authentication")
294
+ self.assertEqual(auth_count_first, 1)
295
+
296
+ self.log_capture.truncate(0)
297
+ self.log_capture.seek(0)
298
+
299
+ BaseClient(
300
+ api_key="test_key",
301
+ base_url="https://api.test.com"
302
+ )
303
+
304
+ second_log = self.log_capture.getvalue()
305
+ auth_count_second = second_log.count("Using API Key authentication")
306
+ self.assertEqual(auth_count_second, 0)
307
+
308
+ def test_authentication_logged_again_for_different_config(self):
309
+ """Test that authentication is logged again when configuration changes"""
310
+ BaseClient(
311
+ api_key="test_key",
312
+ base_url="https://api.test.com"
313
+ )
314
+
315
+ first_log = self.log_capture.getvalue()
316
+ self.assertIn("Using API Key authentication", first_log)
317
+
318
+ self.log_capture.truncate(0)
319
+ self.log_capture.seek(0)
320
+
321
+ from pygeai.core.base.clients import BaseClient as BC
322
+ BC._logged_session_config = None
323
+ reset_session()
324
+
325
+ BaseClient(
326
+ base_url="https://api.test.com",
327
+ access_token="oauth_token",
328
+ project_id="project-123"
329
+ )
330
+
331
+ second_log = self.log_capture.getvalue()
332
+ self.assertIn("Using OAuth 2.0 authentication", second_log)
333
+
334
+
335
+ class TestBaseClientSessionInteraction(unittest.TestCase):
336
+ """
337
+ Tests for BaseClient interaction with Session singleton.
338
+
339
+ python -m unittest pygeai.tests.auth.test_client_initialization.TestBaseClientSessionInteraction
340
+ """
341
+
342
+ def setUp(self):
343
+ """Set up test fixtures"""
344
+ reset_session()
345
+ reset_settings()
346
+
347
+ def tearDown(self):
348
+ """Clean up test fixtures"""
349
+ reset_session()
350
+ reset_settings()
351
+
352
+ def test_multiple_clients_share_session(self):
353
+ """Test that multiple clients share the same session instance"""
354
+ client1 = BaseClient(
355
+ api_key="test_key",
356
+ base_url="https://api.test.com"
357
+ )
358
+
359
+ client2 = BaseClient(
360
+ api_key="test_key",
361
+ base_url="https://api.test.com"
362
+ )
363
+
364
+ self.assertIs(client1.session, client2.session)
365
+
366
+ def test_client_with_different_credentials_updates_session(self):
367
+ """Test that creating client with different credentials updates shared session"""
368
+ client1 = BaseClient(
369
+ api_key="first_key",
370
+ base_url="https://first.test.com"
371
+ )
372
+
373
+ self.assertEqual(client1.session.api_key, "first_key")
374
+
375
+ client2 = BaseClient(
376
+ api_key="second_key",
377
+ base_url="https://second.test.com"
378
+ )
379
+
380
+ self.assertEqual(client1.session.api_key, "second_key")
381
+ self.assertEqual(client2.session.api_key, "second_key")
382
+
383
+ def test_api_service_uses_active_token(self):
384
+ """Test that api_service uses the active token from session"""
385
+ client = BaseClient(
386
+ api_key="api_key_123",
387
+ base_url="https://api.test.com",
388
+ access_token="oauth_token_456",
389
+ project_id="project-789",
390
+ allow_mixed_auth=True
391
+ )
392
+
393
+ self.assertEqual(client.api_service.token, "oauth_token_456")
394
+
395
+ def test_api_service_configured_with_session_properties(self):
396
+ """Test that api_service is configured with session properties"""
397
+ client = BaseClient(
398
+ base_url="https://api.test.com",
399
+ access_token="oauth_token",
400
+ project_id="project-123",
401
+ organization_id="org-456"
402
+ )
403
+
404
+ self.assertEqual(client.api_service.base_url, "https://api.test.com")
405
+ self.assertEqual(client.api_service.token, "oauth_token")
406
+ self.assertEqual(client.api_service.project_id, "project-123")
407
+ self.assertEqual(client.api_service.organization_id, "org-456")
408
+
409
+
410
+ if __name__ == '__main__':
411
+ unittest.main()