pygeai 0.6.0b13__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 (217) 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/content/migration.rst +391 -7
  6. pygeai/_docs/source/pygeai.core.common.rst +8 -0
  7. pygeai/_docs/source/pygeai.tests.auth.rst +56 -0
  8. pygeai/_docs/source/pygeai.tests.cli.rst +8 -0
  9. pygeai/admin/clients.py +1 -3
  10. pygeai/analytics/clients.py +1 -1
  11. pygeai/assistant/clients.py +2 -7
  12. pygeai/assistant/data/clients.py +0 -8
  13. pygeai/assistant/data_analyst/clients.py +0 -2
  14. pygeai/assistant/managers.py +1 -1
  15. pygeai/assistant/rag/clients.py +0 -2
  16. pygeai/assistant/rag/mappers.py +9 -11
  17. pygeai/auth/clients.py +26 -7
  18. pygeai/auth/endpoints.py +2 -1
  19. pygeai/chat/clients.py +2 -2
  20. pygeai/chat/managers.py +1 -1
  21. pygeai/cli/commands/admin.py +13 -25
  22. pygeai/cli/commands/analytics.py +56 -88
  23. pygeai/cli/commands/assistant.py +84 -138
  24. pygeai/cli/commands/auth.py +23 -46
  25. pygeai/cli/commands/base.py +0 -1
  26. pygeai/cli/commands/chat.py +218 -209
  27. pygeai/cli/commands/common.py +5 -5
  28. pygeai/cli/commands/configuration.py +79 -29
  29. pygeai/cli/commands/docs.py +3 -4
  30. pygeai/cli/commands/embeddings.py +13 -19
  31. pygeai/cli/commands/evaluation.py +133 -344
  32. pygeai/cli/commands/feedback.py +7 -15
  33. pygeai/cli/commands/files.py +26 -53
  34. pygeai/cli/commands/gam.py +28 -69
  35. pygeai/cli/commands/lab/ai_lab.py +96 -142
  36. pygeai/cli/commands/lab/common.py +1 -1
  37. pygeai/cli/commands/lab/spec.py +12 -32
  38. pygeai/cli/commands/llm.py +9 -18
  39. pygeai/cli/commands/migrate.py +43 -99
  40. pygeai/cli/commands/organization.py +223 -196
  41. pygeai/cli/commands/rag.py +35 -58
  42. pygeai/cli/commands/rerank.py +21 -25
  43. pygeai/cli/commands/secrets.py +39 -67
  44. pygeai/cli/commands/usage_limits.py +50 -136
  45. pygeai/cli/commands/validators.py +1 -1
  46. pygeai/cli/geai.py +32 -3
  47. pygeai/cli/geai_proxy.py +6 -2
  48. pygeai/cli/install_man.py +1 -1
  49. pygeai/cli/parsers.py +1 -1
  50. pygeai/core/base/clients.py +90 -21
  51. pygeai/core/base/mappers.py +39 -55
  52. pygeai/core/base/session.py +134 -22
  53. pygeai/core/common/config.py +50 -13
  54. pygeai/core/common/constants.py +8 -0
  55. pygeai/core/common/exceptions.py +6 -0
  56. pygeai/core/embeddings/clients.py +0 -1
  57. pygeai/core/embeddings/managers.py +0 -1
  58. pygeai/core/feedback/clients.py +0 -2
  59. pygeai/core/feedback/models.py +1 -1
  60. pygeai/core/files/clients.py +0 -3
  61. pygeai/core/files/managers.py +1 -1
  62. pygeai/core/files/mappers.py +4 -5
  63. pygeai/core/llm/clients.py +0 -1
  64. pygeai/core/models.py +4 -4
  65. pygeai/core/plugins/clients.py +0 -3
  66. pygeai/core/plugins/models.py +2 -2
  67. pygeai/core/rerank/clients.py +0 -2
  68. pygeai/core/secrets/clients.py +0 -2
  69. pygeai/core/services/rest.py +80 -14
  70. pygeai/core/singleton.py +24 -0
  71. pygeai/dbg/__init__.py +2 -2
  72. pygeai/dbg/debugger.py +276 -38
  73. pygeai/evaluation/clients.py +2 -4
  74. pygeai/evaluation/dataset/clients.py +0 -1
  75. pygeai/evaluation/plan/clients.py +0 -2
  76. pygeai/evaluation/result/clients.py +0 -2
  77. pygeai/gam/clients.py +1 -3
  78. pygeai/health/clients.py +1 -3
  79. pygeai/lab/clients.py +0 -1
  80. pygeai/lab/managers.py +0 -1
  81. pygeai/lab/models.py +0 -1
  82. pygeai/lab/strategies/clients.py +1 -2
  83. pygeai/lab/tools/clients.py +2 -2
  84. pygeai/lab/tools/mappers.py +1 -1
  85. pygeai/migration/strategies.py +5 -6
  86. pygeai/migration/tools.py +1 -1
  87. pygeai/organization/clients.py +118 -12
  88. pygeai/organization/endpoints.py +1 -0
  89. pygeai/organization/limits/clients.py +4 -6
  90. pygeai/organization/limits/managers.py +1 -4
  91. pygeai/organization/managers.py +2 -2
  92. pygeai/proxy/config.py +1 -0
  93. pygeai/proxy/managers.py +6 -5
  94. pygeai/tests/admin/test_clients.py +11 -11
  95. pygeai/tests/assistants/rag/test_clients.py +1 -1
  96. pygeai/tests/assistants/rag/test_models.py +1 -2
  97. pygeai/tests/assistants/test_clients.py +1 -1
  98. pygeai/tests/assistants/test_managers.py +1 -3
  99. pygeai/tests/auth/test_cli_configuration.py +252 -0
  100. pygeai/tests/auth/test_client_initialization.py +411 -0
  101. pygeai/tests/auth/test_clients.py +29 -27
  102. pygeai/tests/auth/test_config_manager.py +305 -0
  103. pygeai/tests/auth/test_header_injection.py +294 -0
  104. pygeai/tests/auth/test_oauth.py +3 -1
  105. pygeai/tests/auth/test_session_logging.py +119 -0
  106. pygeai/tests/auth/test_session_validation.py +408 -0
  107. pygeai/tests/auth/test_singleton_reset.py +201 -0
  108. pygeai/tests/chat/test_clients.py +1 -1
  109. pygeai/tests/chat/test_iris.py +1 -1
  110. pygeai/tests/chat/test_ui.py +0 -2
  111. pygeai/tests/cli/commands/lab/test_ai_lab.py +1 -3
  112. pygeai/tests/cli/commands/lab/test_common.py +0 -1
  113. pygeai/tests/cli/commands/test_chat.py +1 -1
  114. pygeai/tests/cli/commands/test_common.py +0 -1
  115. pygeai/tests/cli/commands/test_embeddings.py +2 -2
  116. pygeai/tests/cli/commands/test_evaluation.py +1 -9
  117. pygeai/tests/cli/commands/test_llm.py +1 -1
  118. pygeai/tests/cli/commands/test_migrate.py +1 -1
  119. pygeai/tests/cli/commands/test_rerank.py +0 -1
  120. pygeai/tests/cli/commands/test_secrets.py +1 -1
  121. pygeai/tests/cli/commands/test_show_help.py +0 -1
  122. pygeai/tests/cli/commands/test_validators.py +0 -1
  123. pygeai/tests/cli/test_credentials_flag.py +312 -0
  124. pygeai/tests/cli/test_error_handler.py +0 -1
  125. pygeai/tests/core/base/test_mappers.py +2 -2
  126. pygeai/tests/core/base/test_models.py +4 -4
  127. pygeai/tests/core/common/test_config.py +2 -7
  128. pygeai/tests/core/common/test_decorators.py +0 -1
  129. pygeai/tests/core/embeddings/test_managers.py +1 -1
  130. pygeai/tests/core/feedback/test_clients.py +2 -2
  131. pygeai/tests/core/files/test_clients.py +6 -6
  132. pygeai/tests/core/files/test_models.py +0 -1
  133. pygeai/tests/core/files/test_responses.py +0 -1
  134. pygeai/tests/core/llm/test_clients.py +1 -1
  135. pygeai/tests/core/plugins/test_clients.py +4 -4
  136. pygeai/tests/core/rerank/test_mappers.py +1 -3
  137. pygeai/tests/core/secrets/test_clients.py +2 -3
  138. pygeai/tests/core/services/test_rest.py +10 -10
  139. pygeai/tests/core/utils/test_console.py +0 -1
  140. pygeai/tests/dbg/test_debugger.py +95 -8
  141. pygeai/tests/evaluation/dataset/test_clients.py +24 -27
  142. pygeai/tests/evaluation/plan/test_clients.py +16 -18
  143. pygeai/tests/evaluation/result/test_clients.py +4 -5
  144. pygeai/tests/health/test_clients.py +2 -2
  145. pygeai/tests/integration/lab/agents/test_create_agent.py +1 -3
  146. pygeai/tests/integration/lab/agents/test_get_agent.py +1 -1
  147. pygeai/tests/integration/lab/processes/test_create_process.py +2 -2
  148. pygeai/tests/integration/lab/processes/test_create_task.py +2 -3
  149. pygeai/tests/integration/lab/processes/test_delete_process.py +0 -1
  150. pygeai/tests/integration/lab/processes/test_get_process.py +2 -4
  151. pygeai/tests/integration/lab/processes/test_list_process_instances.py +1 -3
  152. pygeai/tests/integration/lab/processes/test_update_process.py +3 -9
  153. pygeai/tests/integration/lab/reasoning_strategies/test_update_reasoning_strategy.py +1 -2
  154. pygeai/tests/integration/lab/tools/test_delete_tool.py +1 -1
  155. pygeai/tests/integration/lab/tools/test_list_tools.py +1 -1
  156. pygeai/tests/integration/lab/tools/test_update_tool.py +1 -1
  157. pygeai/tests/lab/agents/test_clients.py +17 -17
  158. pygeai/tests/lab/processes/test_clients.py +67 -67
  159. pygeai/tests/lab/processes/test_mappers.py +23 -23
  160. pygeai/tests/lab/spec/test_loader.py +0 -2
  161. pygeai/tests/lab/spec/test_parsers.py +1 -2
  162. pygeai/tests/lab/strategies/test_clients.py +10 -10
  163. pygeai/tests/lab/test_managers.py +3 -5
  164. pygeai/tests/lab/test_mappers.py +1 -4
  165. pygeai/tests/lab/tools/test_clients.py +21 -21
  166. pygeai/tests/lab/tools/test_mappers.py +0 -1
  167. pygeai/tests/organization/limits/test_clients.py +33 -33
  168. pygeai/tests/organization/limits/test_managers.py +7 -7
  169. pygeai/tests/organization/test_clients.py +78 -60
  170. pygeai/tests/proxy/test_clients.py +1 -1
  171. pygeai/tests/proxy/test_integration.py +1 -4
  172. pygeai/tests/proxy/test_managers.py +1 -2
  173. pygeai/tests/proxy/test_servers.py +1 -2
  174. pygeai/tests/snippets/assistants/rag/delete_rag_assistant.py +0 -1
  175. pygeai/tests/snippets/assistants/rag/get_documents.py +0 -1
  176. pygeai/tests/snippets/assistants/rag/get_rag_assistant_data.py +0 -1
  177. pygeai/tests/snippets/chat/get_request_status.py +0 -1
  178. pygeai/tests/snippets/dbg/file_debugging.py +72 -0
  179. pygeai/tests/snippets/dbg/module_debugging.py +60 -0
  180. pygeai/tests/snippets/embeddings/cohere_example.py +2 -2
  181. pygeai/tests/snippets/embeddings/openai_base64_example.py +1 -1
  182. pygeai/tests/snippets/evaluation/dataset/complete_workflow_example.py +8 -8
  183. pygeai/tests/snippets/evaluation/plan/complete_workflow_example.py +5 -5
  184. pygeai/tests/snippets/evaluation/result/complete_workflow_example.py +3 -3
  185. pygeai/tests/snippets/lab/agentic_flow_example_1.py +1 -1
  186. pygeai/tests/snippets/lab/agentic_flow_example_2.py +3 -4
  187. pygeai/tests/snippets/lab/agents/create_agent_with_permissions.py +2 -2
  188. pygeai/tests/snippets/lab/agents/delete_agent.py +1 -2
  189. pygeai/tests/snippets/lab/agents/get_agent.py +1 -1
  190. pygeai/tests/snippets/lab/agents/get_agent_with_new_fields.py +10 -10
  191. pygeai/tests/snippets/lab/agents/get_sharing_link.py +0 -1
  192. pygeai/tests/snippets/lab/agents/list_agents.py +1 -1
  193. pygeai/tests/snippets/lab/agents/publish_agent_revision.py +0 -1
  194. pygeai/tests/snippets/lab/agents/update_agent_properties.py +1 -1
  195. pygeai/tests/snippets/lab/crud_ui.py +3 -5
  196. pygeai/tests/snippets/lab/processes/kbs/get_kb.py +0 -1
  197. pygeai/tests/snippets/lab/processes/kbs/list_kbs.py +0 -1
  198. pygeai/tests/snippets/lab/processes/list_processes.py +1 -1
  199. pygeai/tests/snippets/lab/samples/summarize_files.py +0 -3
  200. pygeai/tests/snippets/lab/strategies/get_reasoning_strategy.py +0 -1
  201. pygeai/tests/snippets/lab/strategies/list_reasoning_strategies.py +1 -1
  202. pygeai/tests/snippets/lab/tools/get_tool.py +1 -1
  203. pygeai/tests/snippets/lab/tools/publish_tool_revision.py +0 -1
  204. pygeai/tests/snippets/lab/tools/set_parameters.py +1 -2
  205. pygeai/tests/snippets/lab/use_cases/c_code_fixer_agent_flow.py +2 -3
  206. pygeai/tests/snippets/lab/use_cases/file_summarizer_example_2.py +1 -1
  207. pygeai/tests/snippets/lab/use_cases/update_cli_expert.py +0 -1
  208. pygeai/tests/snippets/lab/use_cases/update_lab_expert.py +0 -1
  209. pygeai/tests/snippets/lab/use_cases/update_web_designer.py +0 -1
  210. pygeai/tests/snippets/lab/use_cases/update_web_reader.py +0 -1
  211. pygeai/tests/snippets/migrate/orchestrator_examples.py +1 -1
  212. {pygeai-0.6.0b13.dist-info → pygeai-0.6.1.dist-info}/METADATA +32 -7
  213. {pygeai-0.6.0b13.dist-info → pygeai-0.6.1.dist-info}/RECORD +217 -206
  214. {pygeai-0.6.0b13.dist-info → pygeai-0.6.1.dist-info}/WHEEL +0 -0
  215. {pygeai-0.6.0b13.dist-info → pygeai-0.6.1.dist-info}/entry_points.txt +0 -0
  216. {pygeai-0.6.0b13.dist-info → pygeai-0.6.1.dist-info}/licenses/LICENSE +0 -0
  217. {pygeai-0.6.0b13.dist-info → pygeai-0.6.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,408 @@
1
+ import unittest
2
+ import warnings
3
+ import logging
4
+ import io
5
+ import tempfile
6
+ import os
7
+
8
+ from pygeai.core.base.session import Session, get_session, reset_session, _validate_alias
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 TestSessionValidation(unittest.TestCase):
15
+ """
16
+ Tests for Session validation, warnings, and error handling.
17
+
18
+ python -m unittest pygeai.tests.auth.test_session_validation.TestSessionValidation
19
+ """
20
+
21
+ def setUp(self):
22
+ """Set up test fixtures"""
23
+ reset_session()
24
+ reset_settings()
25
+
26
+ self.log_capture = io.StringIO()
27
+ self.handler = logging.StreamHandler(self.log_capture)
28
+ self.handler.setLevel(logging.WARNING)
29
+
30
+ self.geai_logger = logging.getLogger('geai')
31
+ self._original_handlers = self.geai_logger.handlers.copy()
32
+ self._original_level = self.geai_logger.level
33
+
34
+ self.geai_logger.handlers = []
35
+ self.geai_logger.addHandler(self.handler)
36
+ self.geai_logger.setLevel(logging.WARNING)
37
+ self.geai_logger.propagate = False
38
+
39
+ def tearDown(self):
40
+ """Clean up test fixtures"""
41
+ self.geai_logger.handlers = self._original_handlers
42
+ self.geai_logger.level = self._original_level
43
+ reset_session()
44
+ reset_settings()
45
+
46
+ def test_oauth_requires_project_id(self):
47
+ """Test that OAuth access_token requires project_id"""
48
+ with self.assertRaises(MissingRequirementException) as context:
49
+ Session(
50
+ base_url="https://api.test.com",
51
+ access_token="oauth_token_123"
52
+ )
53
+
54
+ self.assertIn("project_id", str(context.exception).lower())
55
+
56
+ def test_project_id_without_access_token_warns(self):
57
+ """Test that project_id without access_token issues UserWarning"""
58
+ with warnings.catch_warnings(record=True) as w:
59
+ warnings.simplefilter("always")
60
+
61
+ session = Session(
62
+ api_key="api_key_123",
63
+ base_url="https://api.test.com",
64
+ project_id="project-123"
65
+ )
66
+
67
+ self.assertEqual(len(w), 1)
68
+ self.assertTrue(issubclass(w[0].category, UserWarning))
69
+ self.assertIn("project_id provided without access_token", str(w[0].message))
70
+
71
+ def test_mixed_auth_without_allow_raises_error(self):
72
+ """Test that both api_key and access_token without allow_mixed_auth raises MixedAuthenticationException"""
73
+ with self.assertRaises(MixedAuthenticationException) as context:
74
+ Session(
75
+ api_key="api_key_123",
76
+ base_url="https://api.test.com",
77
+ access_token="oauth_token_123",
78
+ project_id="project-456",
79
+ allow_mixed_auth=False
80
+ )
81
+
82
+ self.assertIn("Cannot specify both", str(context.exception))
83
+
84
+ def test_mixed_auth_with_allow_succeeds(self):
85
+ """Test that mixed auth works when allow_mixed_auth=True"""
86
+ session = Session(
87
+ api_key="api_key_123",
88
+ base_url="https://api.test.com",
89
+ access_token="oauth_token_123",
90
+ project_id="project-456",
91
+ allow_mixed_auth=True
92
+ )
93
+
94
+ self.assertEqual(session.api_key, "api_key_123")
95
+ self.assertEqual(session.access_token, "oauth_token_123")
96
+ self.assertEqual(session.project_id, "project-456")
97
+ self.assertEqual(session.auth_type, AuthType.OAUTH_TOKEN)
98
+
99
+ def test_no_authentication_logs_warning(self):
100
+ """Test that no authentication method logs warning"""
101
+ Session(base_url="https://api.test.com")
102
+
103
+ log_output = self.log_capture.getvalue()
104
+ self.assertIn("No authentication method configured", log_output)
105
+
106
+ def test_no_base_url_logs_warning(self):
107
+ """Test that missing base_url logs warning"""
108
+ Session(api_key="api_key_123")
109
+
110
+ log_output = self.log_capture.getvalue()
111
+ self.assertIn("Cannot instantiate session without base_url", log_output)
112
+
113
+ def test_auth_type_api_key_only(self):
114
+ """Test auth type is API_KEY when only api_key is provided"""
115
+ session = Session(
116
+ api_key="api_key_123",
117
+ base_url="https://api.test.com"
118
+ )
119
+
120
+ self.assertEqual(session.auth_type, AuthType.API_KEY)
121
+ self.assertTrue(session.is_api_key())
122
+ self.assertFalse(session.is_oauth())
123
+
124
+ def test_auth_type_oauth_only(self):
125
+ """Test auth type is OAUTH_TOKEN when OAuth credentials are provided"""
126
+ session = Session(
127
+ base_url="https://api.test.com",
128
+ access_token="oauth_token_123",
129
+ project_id="project-456"
130
+ )
131
+
132
+ self.assertEqual(session.auth_type, AuthType.OAUTH_TOKEN)
133
+ self.assertTrue(session.is_oauth())
134
+ self.assertFalse(session.is_api_key())
135
+
136
+ def test_auth_type_none(self):
137
+ """Test auth type is NONE when no credentials are provided"""
138
+ session = Session(base_url="https://api.test.com")
139
+
140
+ self.assertEqual(session.auth_type, AuthType.NONE)
141
+ self.assertFalse(session.is_oauth())
142
+ self.assertFalse(session.is_api_key())
143
+
144
+ def test_auth_type_oauth_takes_precedence(self):
145
+ """Test that OAuth takes precedence over API key when both are present"""
146
+ session = Session(
147
+ api_key="api_key_123",
148
+ base_url="https://api.test.com",
149
+ access_token="oauth_token_123",
150
+ project_id="project-456",
151
+ allow_mixed_auth=True
152
+ )
153
+
154
+ self.assertEqual(session.auth_type, AuthType.OAUTH_TOKEN)
155
+
156
+ def test_get_active_token_api_key(self):
157
+ """Test get_active_token returns api_key when using API key auth"""
158
+ session = Session(
159
+ api_key="api_key_123",
160
+ base_url="https://api.test.com"
161
+ )
162
+
163
+ self.assertEqual(session.get_active_token(), "api_key_123")
164
+
165
+ def test_get_active_token_oauth(self):
166
+ """Test get_active_token returns access_token when using OAuth"""
167
+ session = Session(
168
+ base_url="https://api.test.com",
169
+ access_token="oauth_token_123",
170
+ project_id="project-456"
171
+ )
172
+
173
+ self.assertEqual(session.get_active_token(), "oauth_token_123")
174
+
175
+ def test_get_active_token_none(self):
176
+ """Test get_active_token returns None when no auth is configured"""
177
+ session = Session(base_url="https://api.test.com")
178
+
179
+ self.assertIsNone(session.get_active_token())
180
+
181
+ def test_setter_updates_auth_type_api_key(self):
182
+ """Test that setting api_key updates auth type"""
183
+ session = Session(base_url="https://api.test.com")
184
+ self.assertEqual(session.auth_type, AuthType.NONE)
185
+
186
+ session.api_key = "new_api_key"
187
+ self.assertEqual(session.auth_type, AuthType.API_KEY)
188
+
189
+ def test_setter_updates_auth_type_oauth(self):
190
+ """Test that setting OAuth properties updates auth type"""
191
+ session = Session(
192
+ api_key="api_key_123",
193
+ base_url="https://api.test.com"
194
+ )
195
+ self.assertEqual(session.auth_type, AuthType.API_KEY)
196
+
197
+ session.access_token = "oauth_token_123"
198
+ session.project_id = "project-456"
199
+ self.assertEqual(session.auth_type, AuthType.OAUTH_TOKEN)
200
+
201
+ def test_alias_defaults_to_default(self):
202
+ """Test that alias defaults to 'default' when not provided"""
203
+ session = Session(
204
+ api_key="api_key_123",
205
+ base_url="https://api.test.com"
206
+ )
207
+
208
+ self.assertEqual(session.alias, "default")
209
+
210
+ def test_alias_can_be_set(self):
211
+ """Test that custom alias can be set"""
212
+ session = Session(
213
+ api_key="api_key_123",
214
+ base_url="https://api.test.com",
215
+ alias="production"
216
+ )
217
+
218
+ self.assertEqual(session.alias, "production")
219
+
220
+ def test_organization_id_can_be_set(self):
221
+ """Test that organization_id can be set and retrieved"""
222
+ session = Session(
223
+ base_url="https://api.test.com",
224
+ access_token="oauth_token_123",
225
+ project_id="project-456",
226
+ organization_id="org-789"
227
+ )
228
+
229
+ self.assertEqual(session.organization_id, "org-789")
230
+
231
+
232
+ class TestGetSession(unittest.TestCase):
233
+ """
234
+ Tests for get_session function and singleton behavior.
235
+
236
+ python -m unittest pygeai.tests.auth.test_session_validation.TestGetSession
237
+ """
238
+
239
+ def setUp(self):
240
+ """Set up test fixtures"""
241
+ reset_session()
242
+ reset_settings()
243
+
244
+ self.log_capture = io.StringIO()
245
+ self.handler = logging.StreamHandler(self.log_capture)
246
+ self.handler.setLevel(logging.WARNING)
247
+
248
+ self.geai_logger = logging.getLogger('geai')
249
+ self._original_handlers = self.geai_logger.handlers.copy()
250
+ self._original_level = self.geai_logger.level
251
+
252
+ self.geai_logger.handlers = []
253
+ self.geai_logger.addHandler(self.handler)
254
+ self.geai_logger.setLevel(logging.WARNING)
255
+ self.geai_logger.propagate = False
256
+
257
+ def tearDown(self):
258
+ """Clean up test fixtures"""
259
+ self.geai_logger.handlers = self._original_handlers
260
+ self.geai_logger.level = self._original_level
261
+ reset_session()
262
+ reset_settings()
263
+
264
+ def test_get_session_is_singleton(self):
265
+ """Test that get_session returns the same instance"""
266
+ with tempfile.NamedTemporaryFile(mode='w', suffix='_credentials', delete=False) as f:
267
+ creds_file = f.name
268
+ f.write('[default]\n')
269
+ f.write('GEAI_API_KEY = test_key\n')
270
+ f.write('GEAI_API_BASE_URL = https://test.example.com\n')
271
+
272
+ try:
273
+ reset_settings()
274
+ get_settings(credentials_file=creds_file)
275
+
276
+ session1 = get_session()
277
+ session2 = get_session()
278
+
279
+ self.assertIs(session1, session2)
280
+ finally:
281
+ os.unlink(creds_file)
282
+
283
+ def test_get_session_loads_from_config(self):
284
+ """Test that get_session loads credentials from config file"""
285
+ with tempfile.NamedTemporaryFile(mode='w', suffix='_credentials', delete=False) as f:
286
+ creds_file = f.name
287
+ f.write('[default]\n')
288
+ f.write('GEAI_API_KEY = config_key_123\n')
289
+ f.write('GEAI_API_BASE_URL = https://config.example.com\n')
290
+
291
+ try:
292
+ reset_settings()
293
+ get_settings(credentials_file=creds_file)
294
+
295
+ session = get_session()
296
+
297
+ self.assertEqual(session.api_key, "config_key_123")
298
+ self.assertEqual(session.base_url, "https://config.example.com")
299
+ finally:
300
+ os.unlink(creds_file)
301
+
302
+ def test_get_session_loads_oauth_from_config(self):
303
+ """Test that get_session loads OAuth credentials from config"""
304
+ with tempfile.NamedTemporaryFile(mode='w', suffix='_credentials', delete=False) as f:
305
+ creds_file = f.name
306
+ f.write('[default]\n')
307
+ f.write('GEAI_OAUTH_ACCESS_TOKEN = oauth_token_123\n')
308
+ f.write('GEAI_PROJECT_ID = project-456\n')
309
+ f.write('GEAI_API_BASE_URL = https://oauth.example.com\n')
310
+
311
+ try:
312
+ reset_settings()
313
+ get_settings(credentials_file=creds_file)
314
+
315
+ session = get_session()
316
+
317
+ self.assertEqual(session.access_token, "oauth_token_123")
318
+ self.assertEqual(session.project_id, "project-456")
319
+ self.assertEqual(session.auth_type, AuthType.OAUTH_TOKEN)
320
+ finally:
321
+ os.unlink(creds_file)
322
+
323
+ def test_get_session_warns_about_mixed_auth(self):
324
+ """Test that get_session warns when both API key and OAuth are configured"""
325
+ with tempfile.NamedTemporaryFile(mode='w', suffix='_credentials', delete=False) as f:
326
+ creds_file = f.name
327
+ f.write('[default]\n')
328
+ f.write('GEAI_API_KEY = api_key_123\n')
329
+ f.write('GEAI_OAUTH_ACCESS_TOKEN = oauth_token_123\n')
330
+ f.write('GEAI_PROJECT_ID = project-456\n')
331
+ f.write('GEAI_API_BASE_URL = https://mixed.example.com\n')
332
+
333
+ try:
334
+ reset_settings()
335
+ get_settings(credentials_file=creds_file)
336
+
337
+ session = get_session()
338
+
339
+ log_output = self.log_capture.getvalue()
340
+ self.assertIn("Both API key and OAuth token configured", log_output)
341
+ self.assertIn("OAuth token will take precedence", log_output)
342
+ finally:
343
+ os.unlink(creds_file)
344
+
345
+ def test_get_session_updates_existing_with_alias(self):
346
+ """Test that get_session with alias updates existing session"""
347
+ with tempfile.NamedTemporaryFile(mode='w', suffix='_credentials', delete=False) as f:
348
+ creds_file = f.name
349
+ f.write('[default]\n')
350
+ f.write('GEAI_API_KEY = default_key\n')
351
+ f.write('GEAI_API_BASE_URL = https://default.example.com\n')
352
+ f.write('\n')
353
+ f.write('[staging]\n')
354
+ f.write('GEAI_API_KEY = staging_key\n')
355
+ f.write('GEAI_API_BASE_URL = https://staging.example.com\n')
356
+
357
+ try:
358
+ reset_settings()
359
+ get_settings(credentials_file=creds_file)
360
+
361
+ session1 = get_session('default')
362
+ self.assertEqual(session1.api_key, "default_key")
363
+
364
+ session2 = get_session('staging')
365
+ self.assertEqual(session2.api_key, "staging_key")
366
+
367
+ self.assertIs(session1, session2)
368
+ finally:
369
+ os.unlink(creds_file)
370
+
371
+ def test_validate_alias_raises_for_missing(self):
372
+ """Test that _validate_alias raises exception for non-existent alias"""
373
+ with tempfile.NamedTemporaryFile(mode='w', suffix='_credentials', delete=False) as f:
374
+ creds_file = f.name
375
+ f.write('[default]\n')
376
+ f.write('GEAI_API_KEY = test_key\n')
377
+ f.write('GEAI_API_BASE_URL = https://test.example.com\n')
378
+
379
+ try:
380
+ reset_settings()
381
+ get_settings(credentials_file=creds_file)
382
+
383
+ with self.assertRaises(MissingRequirementException) as context:
384
+ _validate_alias('nonexistent', allow_missing_default=False)
385
+
386
+ self.assertIn("doesn't exist", str(context.exception))
387
+ finally:
388
+ os.unlink(creds_file)
389
+
390
+ def test_validate_alias_allows_missing_default(self):
391
+ """Test that _validate_alias allows missing 'default' when flag is set"""
392
+ with tempfile.NamedTemporaryFile(mode='w', suffix='_credentials', delete=False) as f:
393
+ creds_file = f.name
394
+ f.write('[production]\n')
395
+ f.write('GEAI_API_KEY = prod_key\n')
396
+ f.write('GEAI_API_BASE_URL = https://prod.example.com\n')
397
+
398
+ try:
399
+ reset_settings()
400
+ get_settings(credentials_file=creds_file)
401
+
402
+ _validate_alias('default', allow_missing_default=True)
403
+ finally:
404
+ os.unlink(creds_file)
405
+
406
+
407
+ if __name__ == '__main__':
408
+ unittest.main()
@@ -0,0 +1,201 @@
1
+ import unittest
2
+
3
+ from pygeai.core.singleton import Singleton
4
+ from pygeai.core.base.session import Session, reset_session
5
+ from pygeai.core.common.constants import AuthType
6
+
7
+
8
+ class TestSingletonReset(unittest.TestCase):
9
+ """
10
+ Tests for Singleton reset functionality to ensure proper test isolation.
11
+
12
+ python -m unittest pygeai.tests.auth.test_singleton_reset.TestSingletonReset
13
+ """
14
+
15
+ def setUp(self):
16
+ """Set up test fixtures"""
17
+ reset_session()
18
+
19
+ def tearDown(self):
20
+ """Clean up test fixtures"""
21
+ reset_session()
22
+
23
+ def test_singleton_returns_same_instance(self):
24
+ """Test that Singleton returns the same instance without reset"""
25
+ s1 = Session(api_key="key1", base_url="https://test1.com")
26
+ s2 = Session(api_key="key2", base_url="https://test2.com")
27
+
28
+ self.assertIs(s1, s2)
29
+ self.assertEqual(s1.api_key, "key1")
30
+ self.assertEqual(s2.api_key, "key1")
31
+
32
+ def test_reset_instance_clears_singleton_cache(self):
33
+ """Test that Singleton.reset_instance clears the cache for specific class"""
34
+ s1 = Session(api_key="key1", base_url="https://test1.com")
35
+ self.assertEqual(s1.api_key, "key1")
36
+
37
+ Singleton.reset_instance(Session)
38
+
39
+ s2 = Session(api_key="key2", base_url="https://test2.com")
40
+
41
+ self.assertIsNot(s1, s2)
42
+ self.assertEqual(s2.api_key, "key2")
43
+
44
+ def test_reset_session_clears_singleton_cache(self):
45
+ """Test that reset_session() clears both global variable and singleton cache"""
46
+ s1 = Session(api_key="key1", base_url="https://test1.com")
47
+ self.assertEqual(s1.api_key, "key1")
48
+ self.assertEqual(s1.auth_type, AuthType.API_KEY)
49
+
50
+ reset_session()
51
+
52
+ s2 = Session(
53
+ base_url="https://test2.com",
54
+ access_token="token2",
55
+ project_id="project2"
56
+ )
57
+
58
+ self.assertIsNot(s1, s2)
59
+ self.assertEqual(s2.access_token, "token2")
60
+ self.assertEqual(s2.auth_type, AuthType.OAUTH_TOKEN)
61
+ self.assertIsNone(s2.api_key)
62
+
63
+ def test_reset_allows_different_auth_types(self):
64
+ """Test that reset allows switching between auth types"""
65
+ s1 = Session(api_key="api_key", base_url="https://test.com")
66
+ self.assertEqual(s1.auth_type, AuthType.API_KEY)
67
+
68
+ reset_session()
69
+
70
+ s2 = Session(
71
+ base_url="https://test.com",
72
+ access_token="oauth_token",
73
+ project_id="project_id"
74
+ )
75
+ self.assertEqual(s2.auth_type, AuthType.OAUTH_TOKEN)
76
+
77
+ reset_session()
78
+
79
+ s3 = Session(base_url="https://test.com")
80
+ self.assertEqual(s3.auth_type, AuthType.NONE)
81
+
82
+ def test_reset_session_allows_clean_state(self):
83
+ """Test that reset_session creates truly independent instances"""
84
+ s1 = Session(
85
+ api_key="key1",
86
+ base_url="https://test1.com",
87
+ access_token="token1",
88
+ project_id="project1",
89
+ organization_id="org1",
90
+ allow_mixed_auth=True
91
+ )
92
+
93
+ self.assertEqual(s1.api_key, "key1")
94
+ self.assertEqual(s1.access_token, "token1")
95
+ self.assertEqual(s1.project_id, "project1")
96
+ self.assertEqual(s1.organization_id, "org1")
97
+
98
+ reset_session()
99
+
100
+ s2 = Session(base_url="https://test2.com")
101
+
102
+ self.assertIsNot(s1, s2)
103
+ self.assertIsNone(s2.api_key)
104
+ self.assertIsNone(s2.access_token)
105
+ self.assertIsNone(s2.project_id)
106
+ self.assertIsNone(s2.organization_id)
107
+ self.assertEqual(s2.auth_type, AuthType.NONE)
108
+
109
+ def test_reset_all_instances_clears_everything(self):
110
+ """Test that reset_all_instances clears all singleton caches"""
111
+ s1 = Session(api_key="key1", base_url="https://test1.com")
112
+
113
+ Singleton.reset_all_instances()
114
+
115
+ s2 = Session(api_key="key2", base_url="https://test2.com")
116
+
117
+ self.assertIsNot(s1, s2)
118
+ self.assertEqual(s2.api_key, "key2")
119
+
120
+ def test_singleton_cache_persists_without_reset(self):
121
+ """Test that without reset, singleton cache persists"""
122
+ s1 = Session(api_key="persistent_key", base_url="https://test.com")
123
+
124
+ s2 = Session(api_key="ignored_key", base_url="https://ignored.com")
125
+
126
+ self.assertIs(s1, s2)
127
+ self.assertEqual(s2.api_key, "persistent_key")
128
+ self.assertEqual(s2.base_url, "https://test.com")
129
+
130
+ def test_reset_does_not_affect_other_sessions(self):
131
+ """Test that reset only affects Session class singleton"""
132
+ s1 = Session(api_key="key1", base_url="https://test1.com")
133
+
134
+ reset_session()
135
+
136
+ s2 = Session(api_key="key2", base_url="https://test2.com")
137
+
138
+ self.assertIsNot(s1, s2)
139
+ self.assertNotEqual(s1.api_key, s2.api_key)
140
+
141
+
142
+ class TestSingletonResetSequence(unittest.TestCase):
143
+ """
144
+ Tests that simulate the sequence of tests running to verify isolation.
145
+
146
+ python -m unittest pygeai.tests.auth.test_singleton_reset.TestSingletonResetSequence
147
+ """
148
+
149
+ def setUp(self):
150
+ """Set up test fixtures"""
151
+ reset_session()
152
+
153
+ def tearDown(self):
154
+ """Clean up test fixtures"""
155
+ reset_session()
156
+
157
+ def test_sequence_1_oauth_session(self):
158
+ """Simulates a test creating OAuth session"""
159
+ session = Session(
160
+ base_url="https://test.com",
161
+ access_token="oauth_token",
162
+ project_id="project_id"
163
+ )
164
+
165
+ self.assertEqual(session.auth_type, AuthType.OAUTH_TOKEN)
166
+ self.assertEqual(session.access_token, "oauth_token")
167
+
168
+ def test_sequence_2_api_key_session(self):
169
+ """Simulates next test creating API key session - should not see OAuth"""
170
+ session = Session(
171
+ api_key="api_key",
172
+ base_url="https://test.com"
173
+ )
174
+
175
+ self.assertEqual(session.auth_type, AuthType.API_KEY)
176
+ self.assertEqual(session.api_key, "api_key")
177
+ self.assertIsNone(session.access_token)
178
+
179
+ def test_sequence_3_no_auth_session(self):
180
+ """Simulates test creating session with no auth - should not see previous auth"""
181
+ session = Session(base_url="https://test.com")
182
+
183
+ self.assertEqual(session.auth_type, AuthType.NONE)
184
+ self.assertIsNone(session.api_key)
185
+ self.assertIsNone(session.access_token)
186
+
187
+ def test_sequence_4_different_oauth_session(self):
188
+ """Simulates test creating different OAuth session - should not see previous"""
189
+ session = Session(
190
+ base_url="https://test.com",
191
+ access_token="different_token",
192
+ project_id="different_project"
193
+ )
194
+
195
+ self.assertEqual(session.auth_type, AuthType.OAUTH_TOKEN)
196
+ self.assertEqual(session.access_token, "different_token")
197
+ self.assertEqual(session.project_id, "different_project")
198
+
199
+
200
+ if __name__ == '__main__':
201
+ unittest.main()
@@ -1,5 +1,5 @@
1
1
  import unittest
2
- from unittest.mock import patch, MagicMock
2
+ from unittest.mock import MagicMock
3
3
  import json
4
4
  from pygeai.chat.clients import ChatClient
5
5
  from pygeai.core.common.exceptions import InvalidAPIResponseException
@@ -1,5 +1,5 @@
1
1
  import unittest
2
- from unittest.mock import patch, MagicMock
2
+ from unittest.mock import MagicMock
3
3
  from pygeai.chat.iris import Iris
4
4
 
5
5
 
@@ -3,7 +3,6 @@ import os
3
3
  import json
4
4
  from datetime import datetime
5
5
  from unittest.mock import patch, MagicMock
6
- import streamlit as st
7
6
  from pygeai.chat.ui import (
8
7
  parse_args,
9
8
  save_session_to_file,
@@ -17,7 +16,6 @@ from pygeai.chat.ui import (
17
16
  save_recent_agents,
18
17
  load_recent_agents
19
18
  )
20
- from pygeai.lab.models import AgentList, Agent
21
19
 
22
20
 
23
21
  class TestStreamlitChat(unittest.TestCase):
@@ -1,6 +1,5 @@
1
1
  import unittest
2
2
  from unittest.mock import patch, MagicMock
3
- import json
4
3
  from pygeai.cli.commands.lab.ai_lab import (
5
4
  show_help,
6
5
  list_agents,
@@ -38,7 +37,6 @@ from pygeai.cli.commands.lab.ai_lab import (
38
37
  start_instance,
39
38
  abort_instance,
40
39
  get_instance,
41
- get_instance_history,
42
40
  get_thread_information,
43
41
  send_user_signal,
44
42
  create_kb,
@@ -91,7 +89,7 @@ class TestAILab(unittest.TestCase):
91
89
  option_list = [(Option("project_id", ["--project-id"], "", True), "proj123")]
92
90
  with self.assertRaises(MissingRequirementException) as cm:
93
91
  create_agent(option_list)
94
- self.assertEqual(str(cm.exception), "Cannot create assistant without specifying name, access scope and public name")
92
+ self.assertEqual(str(cm.exception), "Cannot create assistant without specifying name.")
95
93
 
96
94
  def test_create_agent_invalid_input_json(self):
97
95
  option_list = [
@@ -1,4 +1,3 @@
1
- import unittest
2
1
  from unittest import TestCase
3
2
  from pygeai.cli.commands.lab.common import (
4
3
  get_agent_data_prompt_inputs,
@@ -1,6 +1,6 @@
1
1
  import unittest
2
2
  from unittest.mock import patch, MagicMock
3
- from pygeai.cli.commands.chat import show_help, get_chat_completion, chat_with_iris, chat_with_agent
3
+ from pygeai.cli.commands.chat import get_chat_completion, chat_with_iris, chat_with_agent
4
4
  from pygeai.core.common.exceptions import MissingRequirementException, WrongArgumentError
5
5
  from pygeai.cli.commands import Option
6
6
 
@@ -1,5 +1,4 @@
1
1
  import unittest
2
- from unittest.mock import patch
3
2
 
4
3
  from pygeai.cli.commands.common import (
5
4
  get_llm_settings,