pygeai 0.6.0b7__py3-none-any.whl → 0.6.0b11__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 (178) hide show
  1. pygeai/_docs/source/conf.py +78 -6
  2. pygeai/_docs/source/content/api_reference/embeddings.rst +31 -1
  3. pygeai/_docs/source/content/api_reference/evaluation.rst +590 -0
  4. pygeai/_docs/source/content/api_reference/feedback.rst +237 -0
  5. pygeai/_docs/source/content/api_reference/files.rst +592 -0
  6. pygeai/_docs/source/content/api_reference/gam.rst +401 -0
  7. pygeai/_docs/source/content/api_reference/proxy.rst +318 -0
  8. pygeai/_docs/source/content/api_reference/secrets.rst +495 -0
  9. pygeai/_docs/source/content/api_reference/usage_limits.rst +390 -0
  10. pygeai/_docs/source/content/api_reference.rst +7 -0
  11. pygeai/_docs/source/content/debugger.rst +376 -83
  12. pygeai/_docs/source/content/migration.rst +528 -0
  13. pygeai/_docs/source/content/modules.rst +1 -1
  14. pygeai/_docs/source/pygeai.cli.rst +8 -0
  15. pygeai/_docs/source/pygeai.tests.cli.rst +16 -0
  16. pygeai/_docs/source/pygeai.tests.core.embeddings.rst +16 -0
  17. pygeai/_docs/source/pygeai.tests.snippets.chat.rst +40 -0
  18. pygeai/_docs/source/pygeai.tests.snippets.dbg.rst +45 -0
  19. pygeai/_docs/source/pygeai.tests.snippets.embeddings.rst +40 -0
  20. pygeai/_docs/source/pygeai.tests.snippets.evaluation.dataset.rst +197 -0
  21. pygeai/_docs/source/pygeai.tests.snippets.evaluation.plan.rst +133 -0
  22. pygeai/_docs/source/pygeai.tests.snippets.evaluation.result.rst +37 -0
  23. pygeai/_docs/source/pygeai.tests.snippets.evaluation.rst +10 -0
  24. pygeai/_docs/source/pygeai.tests.snippets.rst +1 -0
  25. pygeai/admin/clients.py +5 -0
  26. pygeai/assistant/clients.py +7 -0
  27. pygeai/assistant/data_analyst/clients.py +2 -0
  28. pygeai/assistant/rag/clients.py +11 -0
  29. pygeai/chat/clients.py +236 -25
  30. pygeai/chat/endpoints.py +3 -1
  31. pygeai/cli/commands/chat.py +322 -1
  32. pygeai/cli/commands/embeddings.py +56 -8
  33. pygeai/cli/commands/migrate.py +994 -434
  34. pygeai/cli/error_handler.py +116 -0
  35. pygeai/cli/geai.py +28 -10
  36. pygeai/cli/parsers.py +8 -2
  37. pygeai/core/base/clients.py +3 -1
  38. pygeai/core/common/exceptions.py +11 -10
  39. pygeai/core/embeddings/__init__.py +19 -0
  40. pygeai/core/embeddings/clients.py +17 -2
  41. pygeai/core/embeddings/mappers.py +16 -2
  42. pygeai/core/embeddings/responses.py +9 -2
  43. pygeai/core/feedback/clients.py +1 -0
  44. pygeai/core/files/clients.py +5 -7
  45. pygeai/core/files/managers.py +42 -0
  46. pygeai/core/llm/clients.py +4 -0
  47. pygeai/core/plugins/clients.py +1 -0
  48. pygeai/core/rerank/clients.py +1 -0
  49. pygeai/core/secrets/clients.py +6 -0
  50. pygeai/core/services/rest.py +1 -1
  51. pygeai/dbg/__init__.py +3 -0
  52. pygeai/dbg/debugger.py +565 -70
  53. pygeai/evaluation/clients.py +1 -1
  54. pygeai/evaluation/dataset/clients.py +45 -44
  55. pygeai/evaluation/plan/clients.py +27 -26
  56. pygeai/evaluation/result/clients.py +37 -5
  57. pygeai/gam/clients.py +4 -0
  58. pygeai/health/clients.py +1 -0
  59. pygeai/lab/agents/clients.py +8 -1
  60. pygeai/lab/models.py +3 -3
  61. pygeai/lab/processes/clients.py +21 -0
  62. pygeai/lab/strategies/clients.py +4 -0
  63. pygeai/lab/tools/clients.py +1 -0
  64. pygeai/migration/__init__.py +31 -0
  65. pygeai/migration/strategies.py +404 -155
  66. pygeai/migration/tools.py +170 -3
  67. pygeai/organization/clients.py +13 -0
  68. pygeai/organization/limits/clients.py +15 -0
  69. pygeai/proxy/clients.py +3 -1
  70. pygeai/tests/admin/test_clients.py +16 -11
  71. pygeai/tests/assistants/rag/test_clients.py +35 -23
  72. pygeai/tests/assistants/test_clients.py +22 -15
  73. pygeai/tests/auth/test_clients.py +14 -6
  74. pygeai/tests/chat/test_clients.py +211 -1
  75. pygeai/tests/cli/commands/test_embeddings.py +32 -9
  76. pygeai/tests/cli/commands/test_evaluation.py +7 -0
  77. pygeai/tests/cli/commands/test_migrate.py +112 -243
  78. pygeai/tests/cli/test_error_handler.py +225 -0
  79. pygeai/tests/cli/test_geai_driver.py +154 -0
  80. pygeai/tests/cli/test_parsers.py +5 -5
  81. pygeai/tests/core/embeddings/test_clients.py +144 -0
  82. pygeai/tests/core/embeddings/test_managers.py +171 -0
  83. pygeai/tests/core/embeddings/test_mappers.py +142 -0
  84. pygeai/tests/core/feedback/test_clients.py +2 -0
  85. pygeai/tests/core/files/test_clients.py +1 -0
  86. pygeai/tests/core/llm/test_clients.py +14 -9
  87. pygeai/tests/core/plugins/test_clients.py +5 -3
  88. pygeai/tests/core/rerank/test_clients.py +1 -0
  89. pygeai/tests/core/secrets/test_clients.py +19 -13
  90. pygeai/tests/dbg/test_debugger.py +453 -75
  91. pygeai/tests/evaluation/dataset/test_clients.py +3 -1
  92. pygeai/tests/evaluation/plan/test_clients.py +4 -2
  93. pygeai/tests/evaluation/result/test_clients.py +7 -5
  94. pygeai/tests/gam/test_clients.py +1 -1
  95. pygeai/tests/health/test_clients.py +1 -0
  96. pygeai/tests/lab/agents/test_clients.py +9 -0
  97. pygeai/tests/lab/processes/test_clients.py +36 -0
  98. pygeai/tests/lab/processes/test_mappers.py +3 -0
  99. pygeai/tests/lab/strategies/test_clients.py +14 -9
  100. pygeai/tests/migration/test_strategies.py +45 -218
  101. pygeai/tests/migration/test_tools.py +133 -9
  102. pygeai/tests/organization/limits/test_clients.py +17 -0
  103. pygeai/tests/organization/test_clients.py +22 -0
  104. pygeai/tests/proxy/test_clients.py +2 -0
  105. pygeai/tests/proxy/test_integration.py +1 -0
  106. pygeai/tests/snippets/chat/chat_completion_with_reasoning_effort.py +18 -0
  107. pygeai/tests/snippets/chat/get_response.py +15 -0
  108. pygeai/tests/snippets/chat/get_response_streaming.py +20 -0
  109. pygeai/tests/snippets/chat/get_response_with_files.py +16 -0
  110. pygeai/tests/snippets/chat/get_response_with_tools.py +36 -0
  111. pygeai/tests/snippets/dbg/__init__.py +0 -0
  112. pygeai/tests/snippets/dbg/basic_debugging.py +32 -0
  113. pygeai/tests/snippets/dbg/breakpoint_management.py +48 -0
  114. pygeai/tests/snippets/dbg/stack_navigation.py +45 -0
  115. pygeai/tests/snippets/dbg/stepping_example.py +40 -0
  116. pygeai/tests/snippets/embeddings/cache_example.py +31 -0
  117. pygeai/tests/snippets/embeddings/cohere_example.py +41 -0
  118. pygeai/tests/snippets/embeddings/openai_base64_example.py +27 -0
  119. pygeai/tests/snippets/embeddings/openai_example.py +30 -0
  120. pygeai/tests/snippets/embeddings/similarity_example.py +42 -0
  121. pygeai/tests/snippets/evaluation/dataset/__init__.py +0 -0
  122. pygeai/tests/snippets/evaluation/dataset/complete_workflow_example.py +195 -0
  123. pygeai/tests/snippets/evaluation/dataset/create_dataset.py +26 -0
  124. pygeai/tests/snippets/evaluation/dataset/create_dataset_from_file.py +11 -0
  125. pygeai/tests/snippets/evaluation/dataset/create_dataset_row.py +17 -0
  126. pygeai/tests/snippets/evaluation/dataset/create_expected_source.py +18 -0
  127. pygeai/tests/snippets/evaluation/dataset/create_filter_variable.py +19 -0
  128. pygeai/tests/snippets/evaluation/dataset/delete_dataset.py +9 -0
  129. pygeai/tests/snippets/evaluation/dataset/delete_dataset_row.py +10 -0
  130. pygeai/tests/snippets/evaluation/dataset/delete_expected_source.py +15 -0
  131. pygeai/tests/snippets/evaluation/dataset/delete_filter_variable.py +15 -0
  132. pygeai/tests/snippets/evaluation/dataset/get_dataset.py +9 -0
  133. pygeai/tests/snippets/evaluation/dataset/get_dataset_row.py +10 -0
  134. pygeai/tests/snippets/evaluation/dataset/get_expected_source.py +15 -0
  135. pygeai/tests/snippets/evaluation/dataset/get_filter_variable.py +15 -0
  136. pygeai/tests/snippets/evaluation/dataset/list_dataset_rows.py +9 -0
  137. pygeai/tests/snippets/evaluation/dataset/list_datasets.py +6 -0
  138. pygeai/tests/snippets/evaluation/dataset/list_expected_sources.py +10 -0
  139. pygeai/tests/snippets/evaluation/dataset/list_filter_variables.py +10 -0
  140. pygeai/tests/snippets/evaluation/dataset/update_dataset.py +15 -0
  141. pygeai/tests/snippets/evaluation/dataset/update_dataset_row.py +20 -0
  142. pygeai/tests/snippets/evaluation/dataset/update_expected_source.py +18 -0
  143. pygeai/tests/snippets/evaluation/dataset/update_filter_variable.py +19 -0
  144. pygeai/tests/snippets/evaluation/dataset/upload_dataset_rows_file.py +10 -0
  145. pygeai/tests/snippets/evaluation/plan/__init__.py +0 -0
  146. pygeai/tests/snippets/evaluation/plan/add_plan_system_metric.py +13 -0
  147. pygeai/tests/snippets/evaluation/plan/complete_workflow_example.py +136 -0
  148. pygeai/tests/snippets/evaluation/plan/create_evaluation_plan.py +24 -0
  149. pygeai/tests/snippets/evaluation/plan/create_rag_evaluation_plan.py +22 -0
  150. pygeai/tests/snippets/evaluation/plan/delete_evaluation_plan.py +9 -0
  151. pygeai/tests/snippets/evaluation/plan/delete_plan_system_metric.py +13 -0
  152. pygeai/tests/snippets/evaluation/plan/execute_evaluation_plan.py +11 -0
  153. pygeai/tests/snippets/evaluation/plan/get_evaluation_plan.py +9 -0
  154. pygeai/tests/snippets/evaluation/plan/get_plan_system_metric.py +13 -0
  155. pygeai/tests/snippets/evaluation/plan/get_system_metric.py +9 -0
  156. pygeai/tests/snippets/evaluation/plan/list_evaluation_plans.py +7 -0
  157. pygeai/tests/snippets/evaluation/plan/list_plan_system_metrics.py +9 -0
  158. pygeai/tests/snippets/evaluation/plan/list_system_metrics.py +7 -0
  159. pygeai/tests/snippets/evaluation/plan/update_evaluation_plan.py +22 -0
  160. pygeai/tests/snippets/evaluation/plan/update_plan_system_metric.py +14 -0
  161. pygeai/tests/snippets/evaluation/result/__init__.py +0 -0
  162. pygeai/tests/snippets/evaluation/result/complete_workflow_example.py +150 -0
  163. pygeai/tests/snippets/evaluation/result/get_evaluation_result.py +26 -0
  164. pygeai/tests/snippets/evaluation/result/list_evaluation_results.py +17 -0
  165. pygeai/tests/snippets/migrate/__init__.py +45 -0
  166. pygeai/tests/snippets/migrate/agent_migration.py +110 -0
  167. pygeai/tests/snippets/migrate/assistant_migration.py +64 -0
  168. pygeai/tests/snippets/migrate/orchestrator_examples.py +179 -0
  169. pygeai/tests/snippets/migrate/process_migration.py +64 -0
  170. pygeai/tests/snippets/migrate/project_migration.py +42 -0
  171. pygeai/tests/snippets/migrate/tool_migration.py +64 -0
  172. pygeai/tests/snippets/organization/create_project.py +2 -2
  173. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/METADATA +1 -1
  174. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/RECORD +178 -96
  175. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/WHEEL +0 -0
  176. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/entry_points.txt +0 -0
  177. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/licenses/LICENSE +0 -0
  178. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b11.dist-info}/top_level.txt +0 -0
@@ -1,294 +1,163 @@
1
1
  import unittest
2
2
  from unittest.mock import patch, Mock
3
- from pygeai.cli.commands.migrate import (
4
- show_help,
5
- migrate_base,
6
- clone_project,
7
- clone_agent,
8
- clone_tool,
9
- clone_process,
10
- clone_task,
11
- Option
12
- )
13
- from pygeai.core.common.exceptions import MissingRequirementException
3
+ from pygeai.cli.commands.migrate import clone_project, show_help, migrate_commands
14
4
 
15
5
 
16
6
  class TestMigrateCommands(unittest.TestCase):
17
- """
18
- python -m unittest pygeai.tests.cli.commands.test_migrate.TestMigrateCommands
19
- """
20
- def setUp(self):
21
- # Helper to create Option objects for testing
22
- self.mock_option = lambda name, value: (Option(name, [f"--{name}"], f"Description for {name}", True), value)
23
7
 
24
- @patch('pygeai.cli.commands.migrate.Console.write_stdout')
25
- @patch('pygeai.cli.commands.migrate.build_help_text')
26
- def test_show_help(self, mock_build_help, mock_write_stdout):
27
- mock_help_text = "Mocked help text"
28
- mock_build_help.return_value = mock_help_text
8
+ def mock_option(self, name, value=None):
9
+ option = Mock()
10
+ option.name = name
11
+ return (option, value)
29
12
 
13
+ def test_show_help(self):
30
14
  show_help()
31
15
 
32
- mock_build_help.assert_called_once()
33
- mock_write_stdout.assert_called_once_with(mock_help_text)
34
-
35
- def test_migrate_base_success(self):
36
- option_list = [
37
- self.mock_option("from_api_key", "from_key"),
38
- self.mock_option("from_instance", "from_instance"),
39
- self.mock_option("to_api_key", "to_key"),
40
- self.mock_option("to_instance", "to_instance")
41
- ]
42
-
43
- result = migrate_base(option_list)
44
-
45
- self.assertEqual(result, ("from_key", "from_instance", "to_key", "to_instance"))
46
-
47
- def test_migrate_base_missing_source(self):
48
- option_list = [
49
- self.mock_option("to_api_key", "to_key"),
50
- self.mock_option("to_instance", "to_instance")
51
- ]
52
-
53
- with self.assertRaises(MissingRequirementException) as context:
54
- migrate_base(option_list)
55
-
56
- self.assertEqual(str(context.exception), "Cannot migrate resources without indicating source: API key and instance")
57
-
16
+ @patch('pygeai.auth.clients.AuthClient')
58
17
  @patch('pygeai.cli.commands.migrate.Console.write_stdout')
59
18
  @patch('pygeai.cli.commands.migrate.MigrationTool')
60
- @patch('pygeai.cli.commands.migrate.ProjectMigrationStrategy')
61
- def test_clone_project_success(self, mock_strategy, mock_tool, mock_write_stdout):
62
- mock_strategy_instance = Mock()
63
- mock_strategy.return_value = mock_strategy_instance
64
- mock_tool_instance = Mock()
65
- mock_tool.return_value = mock_tool_instance
66
- mock_tool_instance.run_migration.return_value = {"status": "success"}
19
+ @patch('pygeai.cli.commands.migrate.MigrationOrchestrator')
20
+ @patch('pygeai.cli.commands.migrate.RAGAssistantClient')
21
+ @patch('pygeai.cli.commands.migrate.FileManager')
22
+ @patch('pygeai.cli.commands.migrate.AILabManager')
23
+ @patch("pygeai.admin.clients.AdminClient.validate_api_token", return_value={"projectId": "test_project"})
24
+ def test_clone_project_with_all_flag(self, mock_base_client, mock_lab_mgr, mock_file_mgr, mock_rag_client, mock_orchestrator, mock_migration_tool, mock_stdout, mock_auth_client):
25
+ mock_lab_instance = Mock()
26
+ mock_lab_mgr.return_value = mock_lab_instance
27
+
28
+ mock_lab_instance.get_agent_list.return_value = Mock(agents=[Mock(id="agent1")])
29
+ mock_lab_instance.list_tools.return_value = Mock(tools=[Mock(id="tool1")])
30
+ mock_lab_instance.list_processes.return_value = Mock(processes=[Mock(id="proc1")])
31
+ mock_lab_instance.list_tasks.return_value = Mock(tasks=[Mock(id="task1")])
32
+
33
+ mock_rag_instance = Mock()
34
+ mock_rag_client.return_value = mock_rag_instance
35
+ mock_rag_instance.get_assistants_from_project.return_value = {
36
+ "assistants": [
37
+ {
38
+ "name": "asst1",
39
+ "searchOptions": {},
40
+ "indexOptions": {},
41
+ "welcomeData": None,
42
+ "llmSettings": None
43
+ }
44
+ ]
45
+ }
46
+
47
+ mock_file_instance = Mock()
48
+ mock_file_mgr.return_value = mock_file_instance
49
+ mock_file_instance.get_file_list.return_value = Mock(files=[Mock(id="file1")])
50
+
51
+ mock_migration_tool_instance = Mock()
52
+ mock_migration_tool.return_value = mock_migration_tool_instance
53
+ mock_migration_tool_instance.run_migration.return_value = "proj456"
54
+
55
+ mock_auth_instance = Mock()
56
+ mock_auth_client.return_value = mock_auth_instance
57
+ mock_auth_instance.create_project_api_token.return_value = {"id": "new_project_api_key", "name": "Migration API Key"}
58
+
59
+ mock_orch_instance = Mock()
60
+ mock_orchestrator.return_value = mock_orch_instance
61
+ mock_orch_instance.execute.return_value = {"completed": 1, "total": 1, "failed": 0}
62
+
67
63
  option_list = [
68
64
  self.mock_option("from_api_key", "from_key"),
65
+ self.mock_option("from_organization_api_key", "from_org_key"),
69
66
  self.mock_option("from_project_id", "proj123"),
70
67
  self.mock_option("from_instance", "from_instance"),
71
- self.mock_option("to_api_key", "to_key"),
68
+ self.mock_option("from_organization_id", "org123"),
69
+ self.mock_option("to_organization_api_key", "to_org_key"),
72
70
  self.mock_option("to_project_name", "new_project"),
73
- self.mock_option("to_instance", "to_instance"),
74
- self.mock_option("admin_email", "admin@example.com")
71
+ self.mock_option("to_organization_id", "org456"),
72
+ self.mock_option("admin_email", "admin@example.com"),
73
+ self.mock_option("all", True)
75
74
  ]
76
75
 
77
76
  clone_project(option_list)
78
77
 
79
- mock_strategy.assert_called_once_with(
80
- from_api_key="from_key",
81
- from_project_id="proj123",
82
- from_instance="from_instance",
83
- to_api_key="to_key",
84
- to_project_name="new_project",
85
- to_instance="to_instance",
86
- admin_email="admin@example.com"
87
- )
88
- mock_tool.assert_called_once_with(mock_strategy_instance)
89
- mock_tool_instance.run_migration.assert_called_once()
90
- mock_write_stdout.assert_called_once_with("Migration result: \n{'status': 'success'}")
78
+ mock_migration_tool_instance.run_migration.assert_called_once()
79
+ mock_auth_instance.create_project_api_token.assert_called_once()
80
+ mock_lab_instance.get_agent_list.assert_called_once()
81
+ mock_lab_instance.list_tools.assert_called_once()
82
+ mock_lab_instance.list_processes.assert_called_once()
83
+ mock_lab_instance.list_tasks.assert_called_once()
84
+ mock_rag_instance.get_assistants_from_project.assert_called_once()
85
+ mock_file_instance.get_file_list.assert_called_once()
86
+ mock_orch_instance.execute.assert_called_once()
91
87
 
92
- def test_clone_project_missing_source(self):
93
- option_list = [
94
- self.mock_option("to_api_key", "to_key"),
95
- self.mock_option("to_project_name", "new_project"),
96
- self.mock_option("to_instance", "to_instance"),
97
- self.mock_option("admin_email", "admin@example.com")
98
- ]
99
-
100
- with self.assertRaises(MissingRequirementException) as context:
101
- clone_project(option_list)
102
-
103
- self.assertEqual(str(context.exception), "Cannot migrate resources without indicating source: project and instance")
104
-
105
- def test_clone_project_missing_admin_email(self):
106
- option_list = [
107
- self.mock_option("from_api_key", "from_key"),
108
- self.mock_option("from_project_id", "proj123"),
109
- self.mock_option("from_instance", "from_instance"),
110
- self.mock_option("to_api_key", "to_key"),
111
- self.mock_option("to_project_name", "new_project"),
112
- self.mock_option("to_instance", "to_instance")
113
- ]
114
-
115
- with self.assertRaises(MissingRequirementException) as context:
116
- clone_project(option_list)
117
-
118
- self.assertEqual(str(context.exception), "Admin email for new project must be defined.")
88
+ @patch('pygeai.cli.commands.migrate.Console.write_stdout')
89
+ @patch('pygeai.cli.commands.migrate.MigrationOrchestrator')
90
+ @patch('pygeai.cli.commands.migrate.AILabManager')
91
+ @patch("pygeai.admin.clients.AdminClient.validate_api_token", return_value={"projectId": "test_project"})
92
+ def test_clone_project_without_project_creation_no_org_keys_needed(self, mock_base_client, mock_lab_mgr,
93
+ mock_orchestrator, mock_stdout):
94
+ """Test that migration to existing project works without org keys but requires to_api_key"""
95
+ mock_lab_instance = Mock()
96
+ mock_lab_mgr.return_value = mock_lab_instance
97
+
98
+ mock_lab_instance.get_agent_list.return_value = Mock(agents=[Mock(id="agent1")])
99
+
100
+ mock_orch_instance = Mock()
101
+ mock_orchestrator.return_value = mock_orch_instance
102
+ mock_orch_instance.execute.return_value = {"completed": 1, "total": 1, "failed": 0}
119
103
 
120
- @patch('pygeai.cli.commands.migrate.MigrationTool')
121
- @patch('pygeai.cli.commands.migrate.AgentMigrationStrategy')
122
- def test_clone_agent_success(self, mock_strategy, mock_tool):
123
- mock_strategy_instance = Mock()
124
- mock_strategy.return_value = mock_strategy_instance
125
- mock_tool_instance = Mock()
126
- mock_tool.return_value = mock_tool_instance
127
104
  option_list = [
128
105
  self.mock_option("from_api_key", "from_key"),
129
106
  self.mock_option("from_project_id", "proj123"),
130
107
  self.mock_option("from_instance", "from_instance"),
108
+ self.mock_option("to_project_id", "proj456"),
131
109
  self.mock_option("to_api_key", "to_key"),
132
- self.mock_option("to_project_id", "to_proj123"),
133
- self.mock_option("to_instance", "to_instance"),
134
- self.mock_option("agent_id", "agent456")
135
- ]
136
-
137
- clone_agent(option_list)
138
-
139
- mock_strategy.assert_called_once_with(
140
- from_api_key="from_key",
141
- from_project_id="proj123",
142
- from_instance="from_instance",
143
- to_api_key="to_key",
144
- to_project_id="to_proj123",
145
- to_instance="to_instance",
146
- agent_id="agent456"
147
- )
148
- mock_tool.assert_called_once_with(mock_strategy_instance)
149
- mock_tool_instance.run_migration.assert_called_once()
150
-
151
- def test_clone_agent_missing_source(self):
152
- option_list = [
153
- self.mock_option("to_api_key", "to_key"),
154
- self.mock_option("to_project_id", "to_proj123"),
155
- self.mock_option("to_instance", "to_instance")
110
+ self.mock_option("agents", "all")
156
111
  ]
157
112
 
158
- with self.assertRaises(MissingRequirementException) as context:
159
- clone_agent(option_list)
113
+ clone_project(option_list)
160
114
 
161
- self.assertEqual(str(context.exception), "Cannot migrate resources without indicating source: project, instance and agent id")
115
+ mock_lab_instance.get_agent_list.assert_called_once()
116
+ mock_orch_instance.execute.assert_called_once()
162
117
 
163
- @patch('pygeai.cli.commands.migrate.MigrationTool')
164
- @patch('pygeai.cli.commands.migrate.ToolMigrationStrategy')
165
- def test_clone_tool_success(self, mock_strategy, mock_tool):
166
- mock_strategy_instance = Mock()
167
- mock_strategy.return_value = mock_strategy_instance
168
- mock_tool_instance = Mock()
169
- mock_tool.return_value = mock_tool_instance
118
+ def test_clone_project_missing_required_params(self):
119
+ from pygeai.core.common.exceptions import MissingRequirementException
120
+
170
121
  option_list = [
171
122
  self.mock_option("from_api_key", "from_key"),
172
- self.mock_option("from_project_id", "proj123"),
173
- self.mock_option("from_instance", "from_instance"),
174
- self.mock_option("to_api_key", "to_key"),
175
- self.mock_option("to_project_id", "to_proj123"),
176
- self.mock_option("to_instance", "to_instance"),
177
- self.mock_option("tool_id", "tool789")
178
123
  ]
179
124
 
180
- clone_tool(option_list)
181
-
182
- mock_strategy.assert_called_once_with(
183
- from_api_key="from_key",
184
- from_project_id="proj123",
185
- from_instance="from_instance",
186
- to_api_key="to_key",
187
- to_project_id="to_proj123",
188
- to_instance="to_instance",
189
- tool_id="tool789"
190
- )
191
- mock_tool.assert_called_once_with(mock_strategy_instance)
192
- mock_tool_instance.run_migration.assert_called_once()
193
-
194
- def test_clone_tool_missing_source(self):
195
- option_list = [
196
- self.mock_option("to_api_key", "to_key"),
197
- self.mock_option("to_project_id", "to_proj123"),
198
- self.mock_option("to_instance", "to_instance")
199
- ]
200
-
201
- with self.assertRaises(MissingRequirementException) as context:
202
- clone_tool(option_list)
203
-
204
- self.assertEqual(str(context.exception), "Cannot migrate resources without indicating source: project, instance, and tool ID")
125
+ with self.assertRaises(MissingRequirementException):
126
+ clone_project(option_list)
205
127
 
206
- @patch('pygeai.cli.commands.migrate.MigrationTool')
207
- @patch('pygeai.cli.commands.migrate.AgenticProcessMigrationStrategy')
208
- def test_clone_process_success(self, mock_strategy, mock_tool):
209
- mock_strategy_instance = Mock()
210
- mock_strategy.return_value = mock_strategy_instance
211
- mock_tool_instance = Mock()
212
- mock_tool.return_value = mock_tool_instance
128
+ def test_clone_project_missing_org_key_when_creating_project(self):
129
+ from pygeai.core.common.exceptions import MissingRequirementException
130
+
213
131
  option_list = [
214
132
  self.mock_option("from_api_key", "from_key"),
215
133
  self.mock_option("from_project_id", "proj123"),
216
134
  self.mock_option("from_instance", "from_instance"),
217
- self.mock_option("to_api_key", "to_key"),
218
- self.mock_option("to_project_id", "to_proj123"),
219
- self.mock_option("to_instance", "to_instance"),
220
- self.mock_option("process_id", "proc101")
135
+ self.mock_option("to_project_name", "new_project"),
136
+ self.mock_option("admin_email", "admin@example.com"),
137
+ self.mock_option("agents", "all")
221
138
  ]
222
139
 
223
- clone_process(option_list)
224
-
225
- mock_strategy.assert_called_once_with(
226
- from_api_key="from_key",
227
- from_project_id="proj123",
228
- from_instance="from_instance",
229
- to_api_key="to_key",
230
- to_project_id="to_proj123",
231
- to_instance="to_instance",
232
- process_id="proc101"
233
- )
234
- mock_tool.assert_called_once_with(mock_strategy_instance)
235
- mock_tool_instance.run_migration.assert_called_once()
140
+ with self.assertRaises(MissingRequirementException) as cm:
141
+ clone_project(option_list)
142
+
143
+ self.assertIn("organization scope API key", str(cm.exception))
236
144
 
237
- def test_clone_process_missing_source(self):
238
- option_list = [
239
- self.mock_option("to_api_key", "to_key"),
240
- self.mock_option("to_project_id", "to_proj123"),
241
- self.mock_option("to_instance", "to_instance")
242
- ]
243
-
244
- with self.assertRaises(MissingRequirementException) as context:
245
- clone_process(option_list)
246
-
247
- self.assertEqual(str(context.exception), "Cannot migrate resources without indicating source: project, instance, and process ID")
248
-
249
- @patch('pygeai.cli.commands.migrate.Console.write_stdout')
250
- @patch('pygeai.cli.commands.migrate.MigrationTool')
251
- @patch('pygeai.cli.commands.migrate.TaskMigrationStrategy')
252
- def test_clone_task_success(self, mock_strategy, mock_tool, mock_write_stdout):
253
- mock_strategy_instance = Mock()
254
- mock_strategy.return_value = mock_strategy_instance
255
- mock_tool_instance = Mock()
256
- mock_tool.return_value = mock_tool_instance
257
- mock_tool_instance.run_migration.return_value = {"status": "success"}
145
+ def test_clone_project_missing_to_api_key_when_using_existing_project(self):
146
+ from pygeai.core.common.exceptions import MissingRequirementException
147
+
258
148
  option_list = [
259
149
  self.mock_option("from_api_key", "from_key"),
260
150
  self.mock_option("from_project_id", "proj123"),
261
151
  self.mock_option("from_instance", "from_instance"),
262
- self.mock_option("to_api_key", "to_key"),
263
- self.mock_option("to_project_id", "to_proj123"),
264
- self.mock_option("to_instance", "to_instance"),
265
- self.mock_option("task_id", "task202")
152
+ self.mock_option("to_project_id", "proj456"),
153
+ self.mock_option("agents", "all")
266
154
  ]
267
155
 
268
- clone_task(option_list)
269
-
270
- mock_strategy.assert_called_once_with(
271
- from_api_key="from_key",
272
- from_project_id="proj123",
273
- from_instance="from_instance",
274
- to_api_key="to_key",
275
- to_project_id="to_proj123",
276
- to_instance="to_instance",
277
- task_id="task202"
278
- )
279
- mock_tool.assert_called_once_with(mock_strategy_instance)
280
- mock_tool_instance.run_migration.assert_called_once()
281
- mock_write_stdout.assert_called_once_with("Migration result: \n{'status': 'success'}")
282
-
283
- def test_clone_task_missing_source(self):
284
- option_list = [
285
- self.mock_option("to_api_key", "to_key"),
286
- self.mock_option("to_project_id", "to_proj123"),
287
- self.mock_option("to_instance", "to_instance")
288
- ]
289
-
290
- with self.assertRaises(MissingRequirementException) as context:
291
- clone_task(option_list)
156
+ with self.assertRaises(MissingRequirementException) as cm:
157
+ clone_project(option_list)
158
+
159
+ self.assertIn("Destination project API key", str(cm.exception))
292
160
 
293
- self.assertEqual(str(context.exception), "Cannot migrate resources without indicating source: project, instance, and task ID")
294
161
 
162
+ if __name__ == '__main__':
163
+ unittest.main()
@@ -0,0 +1,225 @@
1
+ import unittest
2
+ from unittest import TestCase
3
+ from unittest.mock import MagicMock
4
+
5
+ from pygeai.cli.commands import Command, Option, ArgumentsEnum
6
+ from pygeai.cli.error_handler import ErrorHandler, ExitCode
7
+
8
+
9
+ class TestErrorHandler(TestCase):
10
+ """
11
+ Test suite for the ErrorHandler class.
12
+ Run with: python -m unittest pygeai.tests.cli.test_error_handler.TestErrorHandler
13
+ """
14
+
15
+ def setUp(self):
16
+ self.commands = [
17
+ Command(
18
+ name='help',
19
+ identifiers=['help', 'h'],
20
+ description='Display help',
21
+ action=None,
22
+ additional_args=ArgumentsEnum.NOT_AVAILABLE,
23
+ subcommands=[],
24
+ options=[]
25
+ ),
26
+ Command(
27
+ name='version',
28
+ identifiers=['version', 'v'],
29
+ description='Display version',
30
+ action=None,
31
+ additional_args=ArgumentsEnum.NOT_AVAILABLE,
32
+ subcommands=[],
33
+ options=[]
34
+ ),
35
+ Command(
36
+ name='configure',
37
+ identifiers=['configure', 'config', 'c'],
38
+ description='Configure settings',
39
+ action=None,
40
+ additional_args=ArgumentsEnum.OPTIONAL,
41
+ subcommands=[],
42
+ options=[]
43
+ ),
44
+ ]
45
+
46
+ self.options = [
47
+ Option(
48
+ name='key',
49
+ identifiers=['--key', '-k'],
50
+ description='API key',
51
+ requires_args=True
52
+ ),
53
+ Option(
54
+ name='url',
55
+ identifiers=['--url', '-u'],
56
+ description='API URL',
57
+ requires_args=True
58
+ ),
59
+ ]
60
+
61
+ def test_exit_codes_defined(self):
62
+ """Test that all exit codes are properly defined"""
63
+ self.assertEqual(ExitCode.SUCCESS, 0)
64
+ self.assertEqual(ExitCode.USER_INPUT_ERROR, 1)
65
+ self.assertEqual(ExitCode.MISSING_REQUIREMENT, 2)
66
+ self.assertEqual(ExitCode.SERVICE_ERROR, 3)
67
+ self.assertEqual(ExitCode.KEYBOARD_INTERRUPT, 130)
68
+ self.assertEqual(ExitCode.UNEXPECTED_ERROR, 255)
69
+
70
+ def test_format_error_basic(self):
71
+ """Test basic error formatting"""
72
+ result = ErrorHandler.format_error("Test Error", "Something went wrong")
73
+ self.assertIn("ERROR: Something went wrong", result)
74
+ self.assertIn("Run 'geai help' for usage information.", result)
75
+
76
+ def test_format_error_with_suggestion(self):
77
+ """Test error formatting with suggestion"""
78
+ result = ErrorHandler.format_error(
79
+ "Test Error",
80
+ "Command not found",
81
+ suggestion="Try using 'help' command"
82
+ )
83
+ self.assertIn("ERROR: Command not found", result)
84
+ self.assertIn("→ Try using 'help' command", result)
85
+
86
+ def test_format_error_without_help(self):
87
+ """Test error formatting without help text"""
88
+ result = ErrorHandler.format_error(
89
+ "Test Error",
90
+ "Critical error",
91
+ show_help=False
92
+ )
93
+ self.assertIn("ERROR: Critical error", result)
94
+ self.assertNotIn("Run 'geai help'", result)
95
+
96
+ def test_find_similar_items_exact_match(self):
97
+ """Test fuzzy matching with high similarity"""
98
+ items = ['help', 'version', 'configure']
99
+ similar = ErrorHandler.find_similar_items('halp', items)
100
+ self.assertIn('help', similar)
101
+
102
+ def test_find_similar_items_no_match(self):
103
+ """Test fuzzy matching with no similar items"""
104
+ items = ['help', 'version', 'configure']
105
+ similar = ErrorHandler.find_similar_items('xyz123', items, threshold=0.6)
106
+ self.assertEqual(len(similar), 0)
107
+
108
+ def test_find_similar_items_multiple_matches(self):
109
+ """Test fuzzy matching returns top matches"""
110
+ items = ['configure', 'config', 'configuration', 'help']
111
+ similar = ErrorHandler.find_similar_items('config', items)
112
+ self.assertGreater(len(similar), 0)
113
+ self.assertLessEqual(len(similar), 3)
114
+
115
+ def test_get_available_commands(self):
116
+ """Test extraction of available command identifiers"""
117
+ identifiers = ErrorHandler.get_available_commands(self.commands)
118
+ self.assertIn('help', identifiers)
119
+ self.assertIn('h', identifiers)
120
+ self.assertIn('version', identifiers)
121
+ self.assertIn('v', identifiers)
122
+ self.assertIn('configure', identifiers)
123
+ self.assertIn('config', identifiers)
124
+ self.assertIn('c', identifiers)
125
+
126
+ def test_get_available_options(self):
127
+ """Test extraction of available option identifiers"""
128
+ identifiers = ErrorHandler.get_available_options(self.options)
129
+ self.assertIn('--key', identifiers)
130
+ self.assertIn('-k', identifiers)
131
+ self.assertIn('--url', identifiers)
132
+ self.assertIn('-u', identifiers)
133
+
134
+ def test_handle_unknown_command_with_fuzzy_match(self):
135
+ """Test unknown command error with fuzzy matching suggestion"""
136
+ result = ErrorHandler.handle_unknown_command('halp', self.commands)
137
+ self.assertIn("'halp' is not a valid command", result)
138
+ self.assertIn("Did you mean", result)
139
+ self.assertIn("help", result)
140
+
141
+ def test_handle_unknown_command_no_match(self):
142
+ """Test unknown command error without fuzzy match"""
143
+ result = ErrorHandler.handle_unknown_command('xyz123', self.commands)
144
+ self.assertIn("'xyz123' is not a valid command", result)
145
+ self.assertIn("Available commands:", result)
146
+
147
+ def test_handle_unknown_option_with_fuzzy_match(self):
148
+ """Test unknown option error with fuzzy matching"""
149
+ result = ErrorHandler.handle_unknown_option('--kee', self.options)
150
+ self.assertIn("'--kee' is not a valid option", result)
151
+ self.assertIn("Did you mean", result)
152
+
153
+ def test_handle_unknown_option_no_match(self):
154
+ """Test unknown option error without fuzzy match"""
155
+ result = ErrorHandler.handle_unknown_option('--completely-different-option-xyz123', self.options)
156
+ self.assertIn("'--completely-different-option-xyz123' is not a valid option", result)
157
+ self.assertIn("Available options:", result)
158
+
159
+ def test_handle_missing_requirement(self):
160
+ """Test missing requirement error formatting"""
161
+ result = ErrorHandler.handle_missing_requirement("API key is required")
162
+ self.assertIn("API key is required", result)
163
+ self.assertIn("Please provide all required parameters", result)
164
+
165
+ def test_handle_invalid_agent(self):
166
+ """Test invalid agent error formatting"""
167
+ result = ErrorHandler.handle_invalid_agent("Agent 'test-agent' not found")
168
+ self.assertIn("Failed to retrieve or validate the agent", result)
169
+ self.assertIn("Agent 'test-agent' not found", result)
170
+ self.assertIn("Check your agent configuration", result)
171
+
172
+ def test_handle_wrong_argument(self):
173
+ """Test wrong argument error formatting"""
174
+ usage = "geai <command> [options]"
175
+ result = ErrorHandler.handle_wrong_argument("Invalid format", usage)
176
+ self.assertIn("Invalid format", result)
177
+ self.assertIn("Check the command syntax", result)
178
+
179
+ def test_handle_keyboard_interrupt(self):
180
+ """Test keyboard interrupt message"""
181
+ result = ErrorHandler.handle_keyboard_interrupt()
182
+ self.assertIn("Operation cancelled by user", result)
183
+
184
+ def test_handle_unexpected_error(self):
185
+ """Test unexpected error formatting"""
186
+ exception = ValueError("Test error")
187
+ result = ErrorHandler.handle_unexpected_error(exception)
188
+ self.assertIn("unexpected error occurred", result)
189
+ self.assertIn("Test error", result)
190
+ self.assertIn("geai-sdk@globant.com", result)
191
+
192
+ def test_fuzzy_matching_threshold(self):
193
+ """Test that threshold parameter works correctly"""
194
+ items = ['configure', 'help', 'version']
195
+
196
+ # With high threshold, should not match
197
+ similar_high = ErrorHandler.find_similar_items('xyz', items, threshold=0.9)
198
+ self.assertEqual(len(similar_high), 0)
199
+
200
+ # With low threshold, might match
201
+ similar_low = ErrorHandler.find_similar_items('c', items, threshold=0.3)
202
+ self.assertGreaterEqual(len(similar_low), 0)
203
+
204
+ def test_multiple_command_identifiers_in_suggestions(self):
205
+ """Test that fuzzy matching works with multiple identifiers"""
206
+ result = ErrorHandler.handle_unknown_command('configurr', self.commands)
207
+ # Should suggest 'configure' or 'config'
208
+ self.assertTrue('configure' in result or 'config' in result)
209
+
210
+ def test_error_format_consistency(self):
211
+ """Test that all error handlers produce consistent format"""
212
+ results = [
213
+ ErrorHandler.handle_unknown_command('test', self.commands),
214
+ ErrorHandler.handle_unknown_option('--test', self.options),
215
+ ErrorHandler.handle_missing_requirement("test requirement"),
216
+ ErrorHandler.handle_invalid_agent("test agent"),
217
+ ]
218
+
219
+ for result in results:
220
+ self.assertIn("ERROR:", result)
221
+ self.assertIn("→", result)
222
+
223
+
224
+ if __name__ == '__main__':
225
+ unittest.main()