pygeai 0.6.0b10__py3-none-any.whl → 0.6.0b12__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 (135) hide show
  1. pygeai/_docs/source/content/ai_lab/cli.rst +4 -4
  2. pygeai/_docs/source/content/ai_lab/models.rst +169 -35
  3. pygeai/_docs/source/content/ai_lab/runner.rst +2 -2
  4. pygeai/_docs/source/content/ai_lab/spec.rst +9 -9
  5. pygeai/_docs/source/content/ai_lab/usage.rst +34 -34
  6. pygeai/_docs/source/content/ai_lab.rst +1 -1
  7. pygeai/_docs/source/content/analytics.rst +598 -0
  8. pygeai/_docs/source/content/api_reference/chat.rst +428 -2
  9. pygeai/_docs/source/content/api_reference/embeddings.rst +1 -1
  10. pygeai/_docs/source/content/api_reference/project.rst +184 -0
  11. pygeai/_docs/source/content/api_reference/rag.rst +2 -2
  12. pygeai/_docs/source/content/authentication.rst +295 -0
  13. pygeai/_docs/source/content/cli.rst +79 -2
  14. pygeai/_docs/source/content/debugger.rst +1 -1
  15. pygeai/_docs/source/content/migration.rst +19 -2
  16. pygeai/_docs/source/index.rst +2 -0
  17. pygeai/_docs/source/pygeai.analytics.rst +53 -0
  18. pygeai/_docs/source/pygeai.cli.commands.rst +8 -0
  19. pygeai/_docs/source/pygeai.rst +1 -0
  20. pygeai/_docs/source/pygeai.tests.analytics.rst +45 -0
  21. pygeai/_docs/source/pygeai.tests.auth.rst +8 -0
  22. pygeai/_docs/source/pygeai.tests.rst +1 -1
  23. pygeai/analytics/__init__.py +0 -0
  24. pygeai/analytics/clients.py +505 -0
  25. pygeai/analytics/endpoints.py +35 -0
  26. pygeai/analytics/managers.py +606 -0
  27. pygeai/analytics/mappers.py +207 -0
  28. pygeai/analytics/responses.py +240 -0
  29. pygeai/chat/clients.py +46 -1
  30. pygeai/chat/endpoints.py +1 -0
  31. pygeai/cli/commands/analytics.py +525 -0
  32. pygeai/cli/commands/base.py +16 -0
  33. pygeai/cli/commands/chat.py +95 -0
  34. pygeai/cli/commands/common.py +28 -24
  35. pygeai/cli/commands/migrate.py +75 -6
  36. pygeai/cli/commands/organization.py +265 -0
  37. pygeai/cli/commands/validators.py +144 -1
  38. pygeai/cli/error_handler.py +41 -6
  39. pygeai/cli/geai.py +99 -16
  40. pygeai/cli/parsers.py +75 -31
  41. pygeai/cli/texts/help.py +75 -6
  42. pygeai/core/base/clients.py +18 -4
  43. pygeai/core/base/session.py +46 -7
  44. pygeai/core/common/config.py +25 -2
  45. pygeai/core/common/exceptions.py +64 -1
  46. pygeai/core/services/rest.py +20 -2
  47. pygeai/evaluation/clients.py +5 -3
  48. pygeai/lab/agents/clients.py +3 -3
  49. pygeai/lab/agents/endpoints.py +2 -2
  50. pygeai/lab/agents/mappers.py +50 -2
  51. pygeai/lab/clients.py +5 -2
  52. pygeai/lab/managers.py +7 -9
  53. pygeai/lab/models.py +70 -2
  54. pygeai/lab/tools/clients.py +1 -59
  55. pygeai/migration/__init__.py +3 -1
  56. pygeai/migration/strategies.py +72 -3
  57. pygeai/organization/clients.py +110 -1
  58. pygeai/organization/endpoints.py +11 -7
  59. pygeai/organization/managers.py +134 -2
  60. pygeai/organization/mappers.py +28 -2
  61. pygeai/organization/responses.py +11 -1
  62. pygeai/tests/analytics/__init__.py +0 -0
  63. pygeai/tests/analytics/test_clients.py +86 -0
  64. pygeai/tests/analytics/test_managers.py +94 -0
  65. pygeai/tests/analytics/test_mappers.py +84 -0
  66. pygeai/tests/analytics/test_responses.py +73 -0
  67. pygeai/tests/auth/test_oauth.py +172 -0
  68. pygeai/tests/cli/commands/test_migrate.py +14 -1
  69. pygeai/tests/cli/commands/test_organization.py +69 -1
  70. pygeai/tests/cli/test_error_handler.py +4 -4
  71. pygeai/tests/cli/test_geai_driver.py +1 -1
  72. pygeai/tests/lab/agents/test_mappers.py +128 -1
  73. pygeai/tests/lab/test_models.py +2 -0
  74. pygeai/tests/lab/tools/test_clients.py +2 -31
  75. pygeai/tests/organization/test_clients.py +180 -1
  76. pygeai/tests/organization/test_managers.py +40 -0
  77. pygeai/tests/snippets/analytics/__init__.py +0 -0
  78. pygeai/tests/snippets/analytics/get_agent_usage_per_user.py +16 -0
  79. pygeai/tests/snippets/analytics/get_agents_created_and_modified.py +11 -0
  80. pygeai/tests/snippets/analytics/get_average_cost_per_request.py +10 -0
  81. pygeai/tests/snippets/analytics/get_overall_error_rate.py +10 -0
  82. pygeai/tests/snippets/analytics/get_top_10_agents_by_requests.py +12 -0
  83. pygeai/tests/snippets/analytics/get_total_active_users.py +10 -0
  84. pygeai/tests/snippets/analytics/get_total_cost.py +10 -0
  85. pygeai/tests/snippets/analytics/get_total_requests_per_day.py +12 -0
  86. pygeai/tests/snippets/analytics/get_total_tokens.py +12 -0
  87. pygeai/tests/snippets/chat/get_response_complete_example.py +67 -0
  88. pygeai/tests/snippets/chat/get_response_with_instructions.py +19 -0
  89. pygeai/tests/snippets/chat/get_response_with_metadata.py +24 -0
  90. pygeai/tests/snippets/chat/get_response_with_parallel_tools.py +58 -0
  91. pygeai/tests/snippets/chat/get_response_with_reasoning.py +21 -0
  92. pygeai/tests/snippets/chat/get_response_with_store.py +38 -0
  93. pygeai/tests/snippets/chat/get_response_with_truncation.py +24 -0
  94. pygeai/tests/snippets/lab/agents/create_agent_with_permissions.py +39 -0
  95. pygeai/tests/snippets/lab/agents/create_agent_with_properties.py +46 -0
  96. pygeai/tests/snippets/lab/agents/get_agent_with_new_fields.py +62 -0
  97. pygeai/tests/snippets/lab/agents/update_agent_properties.py +50 -0
  98. pygeai/tests/snippets/organization/add_project_member.py +10 -0
  99. pygeai/tests/snippets/organization/add_project_member_batch.py +44 -0
  100. {pygeai-0.6.0b10.dist-info → pygeai-0.6.0b12.dist-info}/METADATA +1 -1
  101. {pygeai-0.6.0b10.dist-info → pygeai-0.6.0b12.dist-info}/RECORD +105 -95
  102. pygeai/_docs/source/pygeai.tests.snippets.assistants.data_analyst.rst +0 -37
  103. pygeai/_docs/source/pygeai.tests.snippets.assistants.rag.rst +0 -85
  104. pygeai/_docs/source/pygeai.tests.snippets.assistants.rst +0 -78
  105. pygeai/_docs/source/pygeai.tests.snippets.auth.rst +0 -10
  106. pygeai/_docs/source/pygeai.tests.snippets.chat.rst +0 -125
  107. pygeai/_docs/source/pygeai.tests.snippets.dbg.rst +0 -45
  108. pygeai/_docs/source/pygeai.tests.snippets.embeddings.rst +0 -61
  109. pygeai/_docs/source/pygeai.tests.snippets.evaluation.dataset.rst +0 -197
  110. pygeai/_docs/source/pygeai.tests.snippets.evaluation.plan.rst +0 -133
  111. pygeai/_docs/source/pygeai.tests.snippets.evaluation.result.rst +0 -37
  112. pygeai/_docs/source/pygeai.tests.snippets.evaluation.rst +0 -20
  113. pygeai/_docs/source/pygeai.tests.snippets.extras.rst +0 -37
  114. pygeai/_docs/source/pygeai.tests.snippets.files.rst +0 -53
  115. pygeai/_docs/source/pygeai.tests.snippets.gam.rst +0 -21
  116. pygeai/_docs/source/pygeai.tests.snippets.lab.agents.rst +0 -93
  117. pygeai/_docs/source/pygeai.tests.snippets.lab.processes.jobs.rst +0 -21
  118. pygeai/_docs/source/pygeai.tests.snippets.lab.processes.kbs.rst +0 -45
  119. pygeai/_docs/source/pygeai.tests.snippets.lab.processes.rst +0 -46
  120. pygeai/_docs/source/pygeai.tests.snippets.lab.rst +0 -82
  121. pygeai/_docs/source/pygeai.tests.snippets.lab.samples.rst +0 -21
  122. pygeai/_docs/source/pygeai.tests.snippets.lab.strategies.rst +0 -45
  123. pygeai/_docs/source/pygeai.tests.snippets.lab.tools.rst +0 -85
  124. pygeai/_docs/source/pygeai.tests.snippets.lab.use_cases.rst +0 -117
  125. pygeai/_docs/source/pygeai.tests.snippets.migrate.rst +0 -10
  126. pygeai/_docs/source/pygeai.tests.snippets.organization.rst +0 -109
  127. pygeai/_docs/source/pygeai.tests.snippets.rag.rst +0 -85
  128. pygeai/_docs/source/pygeai.tests.snippets.rerank.rst +0 -21
  129. pygeai/_docs/source/pygeai.tests.snippets.rst +0 -32
  130. pygeai/_docs/source/pygeai.tests.snippets.secrets.rst +0 -10
  131. pygeai/_docs/source/pygeai.tests.snippets.usage_limit.rst +0 -77
  132. {pygeai-0.6.0b10.dist-info → pygeai-0.6.0b12.dist-info}/WHEEL +0 -0
  133. {pygeai-0.6.0b10.dist-info → pygeai-0.6.0b12.dist-info}/entry_points.txt +0 -0
  134. {pygeai-0.6.0b10.dist-info → pygeai-0.6.0b12.dist-info}/licenses/LICENSE +0 -0
  135. {pygeai-0.6.0b10.dist-info → pygeai-0.6.0b12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,86 @@
1
+ import unittest
2
+ from unittest.mock import patch, MagicMock
3
+ from pygeai.analytics.clients import AnalyticsClient
4
+
5
+
6
+ class TestAnalyticsClient(unittest.TestCase):
7
+
8
+ def setUp(self):
9
+ self.client = AnalyticsClient()
10
+
11
+ @patch('pygeai.analytics.clients.AnalyticsClient.api_service')
12
+ def test_get_agents_created_and_modified(self, mock_api_service):
13
+ mock_response = MagicMock()
14
+ mock_response.status_code = 200
15
+ mock_response.json.return_value = {"createdAgents": 10, "modifiedAgents": 5}
16
+ mock_api_service.get.return_value = mock_response
17
+
18
+ result = self.client.get_agents_created_and_modified("2024-01-01", "2024-01-31")
19
+
20
+ self.assertEqual(result["createdAgents"], 10)
21
+ self.assertEqual(result["modifiedAgents"], 5)
22
+ mock_api_service.get.assert_called_once()
23
+
24
+ @patch('pygeai.analytics.clients.AnalyticsClient.api_service')
25
+ def test_get_total_requests_per_day(self, mock_api_service):
26
+ mock_response = MagicMock()
27
+ mock_response.status_code = 200
28
+ mock_response.json.return_value = {
29
+ "requestsPerDay": [
30
+ {"date": "2024-01-01", "totalRequests": 100, "totalRequestsWithError": 5}
31
+ ]
32
+ }
33
+ mock_api_service.get.return_value = mock_response
34
+
35
+ result = self.client.get_total_requests_per_day("2024-01-01", "2024-01-31")
36
+
37
+ self.assertIn("requestsPerDay", result)
38
+ mock_api_service.get.assert_called_once()
39
+
40
+ @patch('pygeai.analytics.clients.AnalyticsClient.api_service')
41
+ def test_get_total_requests_per_day_with_agent_name(self, mock_api_service):
42
+ mock_response = MagicMock()
43
+ mock_response.status_code = 200
44
+ mock_response.json.return_value = {
45
+ "requestsPerDay": [
46
+ {"date": "2024-01-01", "totalRequests": 50, "totalRequestsWithError": 2}
47
+ ]
48
+ }
49
+ mock_api_service.get.return_value = mock_response
50
+
51
+ result = self.client.get_total_requests_per_day("2024-01-01", "2024-01-31", agent_name="TestAgent")
52
+
53
+ self.assertIn("requestsPerDay", result)
54
+ mock_api_service.get.assert_called_once()
55
+
56
+ @patch('pygeai.analytics.clients.AnalyticsClient.api_service')
57
+ def test_get_average_cost_per_request(self, mock_api_service):
58
+ mock_response = MagicMock()
59
+ mock_response.status_code = 200
60
+ mock_response.json.return_value = {"averageCost": 2.50}
61
+ mock_api_service.get.return_value = mock_response
62
+
63
+ result = self.client.get_average_cost_per_request("2024-01-01", "2024-01-31")
64
+
65
+ self.assertEqual(result["averageCost"], 2.50)
66
+ mock_api_service.get.assert_called_once()
67
+
68
+ @patch('pygeai.analytics.clients.AnalyticsClient.api_service')
69
+ def test_get_total_tokens(self, mock_api_service):
70
+ mock_response = MagicMock()
71
+ mock_response.status_code = 200
72
+ mock_response.json.return_value = {
73
+ "totalInputTokens": 5000,
74
+ "totalOutputTokens": 3000,
75
+ "totalTokens": 8000
76
+ }
77
+ mock_api_service.get.return_value = mock_response
78
+
79
+ result = self.client.get_total_tokens("2024-01-01", "2024-01-31")
80
+
81
+ self.assertEqual(result["totalTokens"], 8000)
82
+ mock_api_service.get.assert_called_once()
83
+
84
+
85
+ if __name__ == '__main__':
86
+ unittest.main()
@@ -0,0 +1,94 @@
1
+ import unittest
2
+ from unittest.mock import patch
3
+ from pygeai.core.common.exceptions import APIError
4
+ from pygeai.core.handlers import ErrorHandler
5
+ from pygeai.analytics.managers import AnalyticsManager
6
+ from pygeai.analytics.mappers import AnalyticsResponseMapper
7
+ from pygeai.analytics.responses import (
8
+ AgentsCreatedAndModifiedResponse, TotalRequestsPerDayResponse,
9
+ AverageCostPerRequestResponse, NumberOfTokensResponse
10
+ )
11
+
12
+
13
+ class TestAnalyticsManager(unittest.TestCase):
14
+
15
+ def setUp(self):
16
+ self.manager = AnalyticsManager()
17
+ self.error_response = {"errors": [{"id": 404, "description": "Not found"}]}
18
+
19
+ @patch("pygeai.analytics.clients.AnalyticsClient.get_agents_created_and_modified")
20
+ def test_get_agents_created_and_modified(self, mock_get_agents):
21
+ mock_response = AgentsCreatedAndModifiedResponse(createdAgents=10, modifiedAgents=5)
22
+ mock_get_agents.return_value = {"createdAgents": 10, "modifiedAgents": 5}
23
+
24
+ with patch.object(AnalyticsResponseMapper, 'map_to_agents_created_and_modified_response', return_value=mock_response):
25
+ response = self.manager.get_agents_created_and_modified("2024-01-01", "2024-01-31")
26
+
27
+ self.assertIsInstance(response, AgentsCreatedAndModifiedResponse)
28
+ self.assertEqual(response.createdAgents, 10)
29
+ self.assertEqual(response.modifiedAgents, 5)
30
+ mock_get_agents.assert_called_once_with(start_date="2024-01-01", end_date="2024-01-31")
31
+
32
+ @patch("pygeai.analytics.clients.AnalyticsClient.get_agents_created_and_modified")
33
+ def test_get_agents_created_and_modified_error(self, mock_get_agents):
34
+ mock_get_agents.return_value = self.error_response
35
+
36
+ with patch.object(ErrorHandler, 'has_errors', return_value=True):
37
+ with patch.object(ErrorHandler, 'extract_error', return_value="Not found"):
38
+ with self.assertRaises(APIError) as context:
39
+ self.manager.get_agents_created_and_modified("2024-01-01", "2024-01-31")
40
+
41
+ self.assertIn("Error received while retrieving agents created and modified", str(context.exception))
42
+ mock_get_agents.assert_called_once_with(start_date="2024-01-01", end_date="2024-01-31")
43
+
44
+ @patch("pygeai.analytics.clients.AnalyticsClient.get_total_requests_per_day")
45
+ def test_get_total_requests_per_day(self, mock_get_requests):
46
+ mock_response = TotalRequestsPerDayResponse(requestsPerDay=[])
47
+ mock_get_requests.return_value = {"requestsPerDay": []}
48
+
49
+ with patch.object(AnalyticsResponseMapper, 'map_to_total_requests_per_day_response', return_value=mock_response):
50
+ response = self.manager.get_total_requests_per_day("2024-01-01", "2024-01-31")
51
+
52
+ self.assertIsInstance(response, TotalRequestsPerDayResponse)
53
+ mock_get_requests.assert_called_once_with(start_date="2024-01-01", end_date="2024-01-31", agent_name=None)
54
+
55
+ @patch("pygeai.analytics.clients.AnalyticsClient.get_total_requests_per_day")
56
+ def test_get_total_requests_per_day_with_agent_name(self, mock_get_requests):
57
+ mock_response = TotalRequestsPerDayResponse(requestsPerDay=[])
58
+ mock_get_requests.return_value = {"requestsPerDay": []}
59
+
60
+ with patch.object(AnalyticsResponseMapper, 'map_to_total_requests_per_day_response', return_value=mock_response):
61
+ response = self.manager.get_total_requests_per_day("2024-01-01", "2024-01-31", agent_name="TestAgent")
62
+
63
+ self.assertIsInstance(response, TotalRequestsPerDayResponse)
64
+ mock_get_requests.assert_called_once_with(start_date="2024-01-01", end_date="2024-01-31", agent_name="TestAgent")
65
+
66
+ @patch("pygeai.analytics.clients.AnalyticsClient.get_average_cost_per_request")
67
+ def test_get_average_cost_per_request(self, mock_get_cost):
68
+ mock_response = AverageCostPerRequestResponse(averageCost=2.50)
69
+ mock_get_cost.return_value = {"averageCost": 2.50}
70
+
71
+ with patch.object(AnalyticsResponseMapper, 'map_to_average_cost_per_request_response', return_value=mock_response):
72
+ response = self.manager.get_average_cost_per_request("2024-01-01", "2024-01-31")
73
+
74
+ self.assertIsInstance(response, AverageCostPerRequestResponse)
75
+ self.assertEqual(response.averageCost, 2.50)
76
+
77
+ @patch("pygeai.analytics.clients.AnalyticsClient.get_total_tokens")
78
+ def test_get_total_tokens(self, mock_get_tokens):
79
+ mock_response = NumberOfTokensResponse(totalInputTokens=5000, totalOutputTokens=3000, totalTokens=8000)
80
+ mock_get_tokens.return_value = {
81
+ "totalInputTokens": 5000,
82
+ "totalOutputTokens": 3000,
83
+ "totalTokens": 8000
84
+ }
85
+
86
+ with patch.object(AnalyticsResponseMapper, 'map_to_total_tokens_response', return_value=mock_response):
87
+ response = self.manager.get_total_tokens("2024-01-01", "2024-01-31")
88
+
89
+ self.assertIsInstance(response, NumberOfTokensResponse)
90
+ self.assertEqual(response.totalTokens, 8000)
91
+
92
+
93
+ if __name__ == '__main__':
94
+ unittest.main()
@@ -0,0 +1,84 @@
1
+ import unittest
2
+ from pygeai.analytics.mappers import AnalyticsResponseMapper
3
+ from pygeai.analytics.responses import (
4
+ AgentsCreatedAndModifiedResponse, AgentsCreatedAndModifiedPerDayResponse,
5
+ FlowsCreatedAndModifiedResponse, AverageCostPerRequestResponse,
6
+ TotalRequestsPerDayResponse, NumberOfTokensResponse
7
+ )
8
+
9
+
10
+ class TestAnalyticsResponseMapper(unittest.TestCase):
11
+
12
+ def test_map_to_agents_created_and_modified_response(self):
13
+ data = {
14
+ "createdAgents": 15,
15
+ "modifiedAgents": 8
16
+ }
17
+ response = AnalyticsResponseMapper.map_to_agents_created_and_modified_response(data)
18
+ self.assertIsInstance(response, AgentsCreatedAndModifiedResponse)
19
+ self.assertEqual(response.createdAgents, 15)
20
+ self.assertEqual(response.modifiedAgents, 8)
21
+
22
+ def test_map_to_agents_created_and_modified_per_day_response(self):
23
+ data = {
24
+ "agentsCreatedAndModifiedPerDay": [
25
+ {"date": "2024-01-01", "createdAgents": 5, "modifiedAgents": 2},
26
+ {"date": "2024-01-02", "createdAgents": 3, "modifiedAgents": 1}
27
+ ]
28
+ }
29
+ response = AnalyticsResponseMapper.map_to_agents_created_and_modified_per_day_response(data)
30
+ self.assertIsInstance(response, AgentsCreatedAndModifiedPerDayResponse)
31
+ self.assertEqual(len(response.agentsCreatedAndModifiedPerDay), 2)
32
+ self.assertEqual(response.agentsCreatedAndModifiedPerDay[0].date, "2024-01-01")
33
+
34
+ def test_map_to_flows_created_and_modified_response(self):
35
+ data = {
36
+ "createdFlows": 10,
37
+ "modifiedFlows": 5
38
+ }
39
+ response = AnalyticsResponseMapper.map_to_flows_created_and_modified_response(data)
40
+ self.assertIsInstance(response, FlowsCreatedAndModifiedResponse)
41
+ self.assertEqual(response.createdFlows, 10)
42
+ self.assertEqual(response.modifiedFlows, 5)
43
+
44
+ def test_map_to_average_cost_per_request_response(self):
45
+ data = {
46
+ "averageCost": 3.25
47
+ }
48
+ response = AnalyticsResponseMapper.map_to_average_cost_per_request_response(data)
49
+ self.assertIsInstance(response, AverageCostPerRequestResponse)
50
+ self.assertEqual(response.averageCost, 3.25)
51
+
52
+ def test_map_to_total_requests_per_day_response(self):
53
+ data = {
54
+ "requestsPerDay": [
55
+ {"date": "2024-01-01", "totalRequests": 150, "totalRequestsWithError": 10},
56
+ {"date": "2024-01-02", "totalRequests": 200, "totalRequestsWithError": 5}
57
+ ]
58
+ }
59
+ response = AnalyticsResponseMapper.map_to_total_requests_per_day_response(data)
60
+ self.assertIsInstance(response, TotalRequestsPerDayResponse)
61
+ self.assertEqual(len(response.requestsPerDay), 2)
62
+ self.assertEqual(response.requestsPerDay[0].totalRequests, 150)
63
+
64
+ def test_map_to_number_of_tokens_response(self):
65
+ data = {
66
+ "totalInputTokens": 5000,
67
+ "totalOutputTokens": 3000,
68
+ "totalTokens": 8000
69
+ }
70
+ response = AnalyticsResponseMapper.map_to_number_of_tokens_response(data)
71
+ self.assertIsInstance(response, NumberOfTokensResponse)
72
+ self.assertEqual(response.totalInputTokens, 5000)
73
+ self.assertEqual(response.totalOutputTokens, 3000)
74
+ self.assertEqual(response.totalTokens, 8000)
75
+
76
+ def test_map_with_missing_fields(self):
77
+ data = {}
78
+ response = AnalyticsResponseMapper.map_to_agents_created_and_modified_response(data)
79
+ self.assertEqual(response.createdAgents, 0)
80
+ self.assertEqual(response.modifiedAgents, 0)
81
+
82
+
83
+ if __name__ == '__main__':
84
+ unittest.main()
@@ -0,0 +1,73 @@
1
+ import unittest
2
+ from pygeai.analytics.responses import (
3
+ AgentsCreatedAndModifiedResponse, AgentActivityPerDayItem, AgentsCreatedAndModifiedPerDayResponse,
4
+ FlowsCreatedAndModifiedResponse, FlowActivityPerDayItem, FlowsCreatedAndModifiedPerDayResponse,
5
+ ProcessesCreatedAndModifiedResponse, AgentUsagePerUserItem, AgentUsagePerUserResponse,
6
+ AverageCostPerRequestResponse, AverageCostPerUserResponse, TotalRequestsPerDayResponse,
7
+ RequestsPerDayItem
8
+ )
9
+
10
+
11
+ class TestAnalyticsResponses(unittest.TestCase):
12
+
13
+ def test_agents_created_and_modified_response(self):
14
+ response = AgentsCreatedAndModifiedResponse(createdAgents=10, modifiedAgents=5)
15
+ self.assertEqual(response.createdAgents, 10)
16
+ self.assertEqual(response.modifiedAgents, 5)
17
+
18
+ def test_agents_created_and_modified_per_day_response(self):
19
+ items = [
20
+ AgentActivityPerDayItem(date="2024-01-01", createdAgents=5, modifiedAgents=2),
21
+ AgentActivityPerDayItem(date="2024-01-02", createdAgents=3, modifiedAgents=1)
22
+ ]
23
+ response = AgentsCreatedAndModifiedPerDayResponse(agentsCreatedAndModifiedPerDay=items)
24
+ self.assertEqual(len(response.agentsCreatedAndModifiedPerDay), 2)
25
+ self.assertEqual(response.agentsCreatedAndModifiedPerDay[0].date, "2024-01-01")
26
+
27
+ def test_flows_created_and_modified_response(self):
28
+ response = FlowsCreatedAndModifiedResponse(createdFlows=8, modifiedFlows=3)
29
+ self.assertEqual(response.createdFlows, 8)
30
+ self.assertEqual(response.modifiedFlows, 3)
31
+
32
+ def test_flows_created_and_modified_per_day_response(self):
33
+ items = [
34
+ FlowActivityPerDayItem(date="2024-01-01", createdFlows=4, modifiedFlows=1)
35
+ ]
36
+ response = FlowsCreatedAndModifiedPerDayResponse(flowsCreatedAndModifiedPerDay=items)
37
+ self.assertEqual(len(response.flowsCreatedAndModifiedPerDay), 1)
38
+
39
+ def test_processes_created_and_modified_response(self):
40
+ response = ProcessesCreatedAndModifiedResponse(createdProcesses=12, modifiedProcesses=6)
41
+ self.assertEqual(response.createdProcesses, 12)
42
+ self.assertEqual(response.modifiedProcesses, 6)
43
+
44
+ def test_agent_usage_per_user_response(self):
45
+ items = [
46
+ AgentUsagePerUserItem(userId="user1", userName="John Doe", totalCost=100.50, totalRequests=50, totalTokens=1000)
47
+ ]
48
+ response = AgentUsagePerUserResponse(agentUsagePerUser=items)
49
+ self.assertEqual(len(response.agentUsagePerUser), 1)
50
+ self.assertEqual(response.agentUsagePerUser[0].userId, "user1")
51
+ self.assertEqual(response.agentUsagePerUser[0].totalCost, 100.50)
52
+
53
+ def test_average_cost_per_request_response(self):
54
+ response = AverageCostPerRequestResponse(averageCost=2.50)
55
+ self.assertEqual(response.averageCost, 2.50)
56
+
57
+ def test_average_cost_per_user_response(self):
58
+ response = AverageCostPerUserResponse(averageCost=150.75)
59
+ self.assertEqual(response.averageCost, 150.75)
60
+
61
+ def test_total_requests_per_day_response(self):
62
+ items = [
63
+ RequestsPerDayItem(date="2024-01-01", totalRequests=100, totalRequestsWithError=5),
64
+ RequestsPerDayItem(date="2024-01-02", totalRequests=120, totalRequestsWithError=3)
65
+ ]
66
+ response = TotalRequestsPerDayResponse(requestsPerDay=items)
67
+ self.assertEqual(len(response.requestsPerDay), 2)
68
+ self.assertEqual(response.requestsPerDay[0].totalRequests, 100)
69
+ self.assertEqual(response.requestsPerDay[1].totalRequestsWithError, 3)
70
+
71
+
72
+ if __name__ == '__main__':
73
+ unittest.main()
@@ -0,0 +1,172 @@
1
+ import unittest
2
+ from unittest.mock import patch, MagicMock
3
+
4
+ from pygeai.lab.clients import AILabClient
5
+ from pygeai.evaluation.clients import EvaluationClient
6
+ from pygeai.core.secrets.clients import SecretClient
7
+ from pygeai.core.base.clients import BaseClient
8
+ from pygeai.core.common.exceptions import MissingRequirementException
9
+
10
+
11
+ class TestOAuthAuthentication(unittest.TestCase):
12
+ """
13
+ Tests for OAuth authentication support across all clients.
14
+
15
+ python -m unittest pygeai.tests.auth.test_oauth.TestOAuthAuthentication
16
+ """
17
+
18
+ def test_base_client_oauth_initialization(self):
19
+ """Test BaseClient accepts OAuth parameters"""
20
+ client = BaseClient(
21
+ base_url="https://api.test.com",
22
+ access_token="oauth_token_123",
23
+ project_id="project-456"
24
+ )
25
+
26
+ self.assertEqual(client.api_service.token, "oauth_token_123")
27
+ self.assertEqual(client.api_service.project_id, "project-456")
28
+
29
+ def test_base_client_api_key_initialization(self):
30
+ """Test BaseClient backward compatibility with API key"""
31
+ client = BaseClient(
32
+ api_key="api_key_123",
33
+ base_url="https://api.test.com"
34
+ )
35
+
36
+ self.assertEqual(client.api_service.token, "api_key_123")
37
+ self.assertIsNone(client.api_service.project_id)
38
+
39
+ def test_base_client_incomplete_oauth_raises_error(self):
40
+ """Test that providing only access_token without project_id raises error"""
41
+ with self.assertRaises(MissingRequirementException) as context:
42
+ BaseClient(
43
+ base_url="https://api.test.com",
44
+ access_token="oauth_token_123"
45
+ )
46
+
47
+ self.assertIn("project_id is required when using access_token", str(context.exception))
48
+
49
+ def test_oauth_headers_are_added_correctly(self):
50
+ """Test that OAuth headers are added correctly by _add_token_to_headers"""
51
+ client = BaseClient(
52
+ base_url="https://api.test.com",
53
+ access_token="oauth_token_123",
54
+ project_id="project-456"
55
+ )
56
+
57
+ headers = client.api_service._add_token_to_headers({})
58
+
59
+ self.assertEqual(headers.get('Authorization'), 'Bearer oauth_token_123')
60
+ self.assertEqual(headers.get('ProjectId'), 'project-456')
61
+
62
+ def test_api_key_headers_use_bearer(self):
63
+ """Test that API key also uses Bearer format in Authorization header"""
64
+ client = BaseClient(
65
+ api_key="api_key_123",
66
+ base_url="https://api.test.com"
67
+ )
68
+
69
+ headers = client.api_service._add_token_to_headers({})
70
+
71
+ self.assertEqual(headers.get('Authorization'), 'Bearer api_key_123')
72
+ self.assertNotIn('ProjectId', headers)
73
+
74
+ def test_oauth_headers_preserve_existing_headers(self):
75
+ """Test that OAuth headers are added while preserving existing headers"""
76
+ client = BaseClient(
77
+ base_url="https://api.test.com",
78
+ access_token="oauth_token_123",
79
+ project_id="project-456"
80
+ )
81
+
82
+ existing_headers = {'Content-Type': 'application/json', 'Custom-Header': 'value'}
83
+ headers = client.api_service._add_token_to_headers(existing_headers.copy())
84
+
85
+ self.assertEqual(headers.get('Authorization'), 'Bearer oauth_token_123')
86
+ self.assertEqual(headers.get('ProjectId'), 'project-456')
87
+ self.assertEqual(headers.get('Content-Type'), 'application/json')
88
+ self.assertEqual(headers.get('Custom-Header'), 'value')
89
+
90
+ def test_ailab_client_oauth_initialization(self):
91
+ """Test AILabClient accepts OAuth parameters with project_id keyword-only"""
92
+ client = AILabClient(
93
+ base_url="https://api.test.com",
94
+ access_token="oauth_token_123",
95
+ project_id="project-456"
96
+ )
97
+
98
+ self.assertEqual(client.api_service.token, "oauth_token_123")
99
+ self.assertEqual(client.api_service.project_id, "project-456")
100
+ self.assertEqual(client.project_id, "project-456")
101
+
102
+ def test_ailab_client_api_key_with_project_id(self):
103
+ """Test AILabClient with API key and project_id"""
104
+ client = AILabClient(
105
+ api_key="api_key_123",
106
+ base_url="https://api.test.com",
107
+ project_id="project-456"
108
+ )
109
+
110
+ self.assertEqual(client.api_service.token, "api_key_123")
111
+ self.assertEqual(client.project_id, "project-456")
112
+
113
+ def test_evaluation_client_oauth_initialization(self):
114
+ """Test EvaluationClient accepts OAuth parameters"""
115
+ client = EvaluationClient(
116
+ base_url="https://api.test.com",
117
+ eval_url="https://eval.test.com",
118
+ access_token="oauth_token_123",
119
+ project_id="project-456"
120
+ )
121
+
122
+ self.assertEqual(client.api_service.token, "oauth_token_123")
123
+ self.assertEqual(client.api_service.project_id, "project-456")
124
+
125
+ def test_evaluation_client_api_key_backward_compatibility(self):
126
+ """Test EvaluationClient backward compatibility with API key"""
127
+ client = EvaluationClient(
128
+ api_key="api_key_123",
129
+ base_url="https://api.test.com",
130
+ eval_url="https://eval.test.com"
131
+ )
132
+
133
+ self.assertEqual(client.api_service.token, "api_key_123")
134
+
135
+ def test_secret_client_oauth_initialization(self):
136
+ """Test SecretClient accepts OAuth parameters"""
137
+ client = SecretClient(
138
+ base_url="https://api.test.com",
139
+ access_token="oauth_token_123",
140
+ project_id="project-456"
141
+ )
142
+
143
+ self.assertEqual(client.api_service.token, "oauth_token_123")
144
+ self.assertEqual(client.api_service.project_id, "project-456")
145
+
146
+ def test_secret_client_api_key_backward_compatibility(self):
147
+ """Test SecretClient backward compatibility with API key"""
148
+ client = SecretClient(
149
+ api_key="api_key_123",
150
+ base_url="https://api.test.com"
151
+ )
152
+
153
+ self.assertEqual(client.api_service.token, "api_key_123")
154
+
155
+ def test_oauth_token_used_over_api_key_in_headers(self):
156
+ """Test that when OAuth token is used, it's properly formatted"""
157
+ client = BaseClient(
158
+ base_url="https://api.test.com",
159
+ access_token="oauth_token_wins",
160
+ project_id="project-wins"
161
+ )
162
+
163
+ self.assertEqual(client.api_service.token, "oauth_token_wins")
164
+ self.assertEqual(client.api_service.project_id, "project-wins")
165
+
166
+ headers = client.api_service._add_token_to_headers({})
167
+ self.assertEqual(headers['Authorization'], 'Bearer oauth_token_wins')
168
+ self.assertEqual(headers['ProjectId'], 'project-wins')
169
+
170
+
171
+ if __name__ == '__main__':
172
+ unittest.main()
@@ -19,9 +19,10 @@ class TestMigrateCommands(unittest.TestCase):
19
19
  @patch('pygeai.cli.commands.migrate.MigrationOrchestrator')
20
20
  @patch('pygeai.cli.commands.migrate.RAGAssistantClient')
21
21
  @patch('pygeai.cli.commands.migrate.FileManager')
22
+ @patch('pygeai.cli.commands.migrate.SecretClient')
22
23
  @patch('pygeai.cli.commands.migrate.AILabManager')
23
24
  @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
+ def test_clone_project_with_all_flag(self, mock_base_client, mock_lab_mgr, mock_secret_client, mock_file_mgr, mock_rag_client, mock_orchestrator, mock_migration_tool, mock_stdout, mock_auth_client):
25
26
  mock_lab_instance = Mock()
26
27
  mock_lab_mgr.return_value = mock_lab_instance
27
28
 
@@ -48,6 +49,17 @@ class TestMigrateCommands(unittest.TestCase):
48
49
  mock_file_mgr.return_value = mock_file_instance
49
50
  mock_file_instance.get_file_list.return_value = Mock(files=[Mock(id="file1")])
50
51
 
52
+ mock_secret_instance = Mock()
53
+ mock_secret_client.return_value = mock_secret_instance
54
+ mock_secret_instance.list_secrets.return_value = {
55
+ "secrets": [
56
+ {
57
+ "id": "secret1",
58
+ "name": "Test Secret"
59
+ }
60
+ ]
61
+ }
62
+
51
63
  mock_migration_tool_instance = Mock()
52
64
  mock_migration_tool.return_value = mock_migration_tool_instance
53
65
  mock_migration_tool_instance.run_migration.return_value = "proj456"
@@ -83,6 +95,7 @@ class TestMigrateCommands(unittest.TestCase):
83
95
  mock_lab_instance.list_tasks.assert_called_once()
84
96
  mock_rag_instance.get_assistants_from_project.assert_called_once()
85
97
  mock_file_instance.get_file_list.assert_called_once()
98
+ mock_secret_instance.list_secrets.assert_called_once()
86
99
  mock_orch_instance.execute.assert_called_once()
87
100
 
88
101
  @patch('pygeai.cli.commands.migrate.Console.write_stdout')
@@ -1,5 +1,5 @@
1
1
  import unittest
2
- from unittest.mock import patch, Mock
2
+ from unittest.mock import patch, Mock, mock_open
3
3
  from pygeai.cli.commands.organization import (
4
4
  show_help,
5
5
  list_assistants,
@@ -10,6 +10,8 @@ from pygeai.cli.commands.organization import (
10
10
  delete_project,
11
11
  get_project_tokens,
12
12
  export_request_data,
13
+ add_project_member,
14
+ add_project_member_in_batch,
13
15
  Option
14
16
  )
15
17
  from pygeai.core.common.exceptions import MissingRequirementException
@@ -206,3 +208,69 @@ class TestOrganizationCommands(unittest.TestCase):
206
208
  mock_instance.export_request_data.assert_called_once_with("assistant1", "completed", "10", "20")
207
209
  mock_write_stdout.assert_called_once_with("Request data: \n{'data': ['request1', 'request2']}")
208
210
 
211
+ @patch('pygeai.cli.commands.organization.Console.write_stdout')
212
+ @patch('pygeai.cli.commands.organization.OrganizationClient')
213
+ def test_add_project_member_success(self, mock_client, mock_write_stdout):
214
+ mock_instance = Mock()
215
+ mock_client.return_value = mock_instance
216
+ mock_instance.add_project_member.return_value = {"status": "invitation sent"}
217
+ option_list = [
218
+ self.mock_option("project_id", "proj123"),
219
+ self.mock_option("user_email", "user@example.com"),
220
+ self.mock_option("roles", "Project member,Project administrator")
221
+ ]
222
+
223
+ add_project_member(option_list)
224
+
225
+ mock_instance.add_project_member.assert_called_once_with("proj123", "user@example.com", ["Project member", "Project administrator"])
226
+ mock_write_stdout.assert_called_once_with("User invitation sent: \n{'status': 'invitation sent'}")
227
+
228
+ def test_add_project_member_missing_fields(self):
229
+ option_list = [
230
+ self.mock_option("project_id", "proj123")
231
+ ]
232
+
233
+ with self.assertRaises(MissingRequirementException) as context:
234
+ add_project_member(option_list)
235
+
236
+ self.assertEqual(str(context.exception), "Cannot add project member without project-id, user email, and roles")
237
+
238
+ @patch('pygeai.cli.commands.organization.Console.write_stdout')
239
+ @patch('pygeai.cli.commands.organization.OrganizationClient')
240
+ @patch('builtins.open', new_callable=mock_open, read_data='proj1,user1@example.com,Project member\nproj2,user2@example.com,Project member,Project administrator\n')
241
+ @patch('os.path.exists', return_value=True)
242
+ def test_add_project_member_batch_success(self, mock_exists, mock_file, mock_client, mock_write_stdout):
243
+ mock_instance = Mock()
244
+ mock_client.return_value = mock_instance
245
+ mock_instance.add_project_member.return_value = {"status": "invitation sent"}
246
+ option_list = [
247
+ self.mock_option("batch_file", "test.csv")
248
+ ]
249
+
250
+ add_project_member(option_list)
251
+
252
+ self.assertEqual(mock_instance.add_project_member.call_count, 2)
253
+ mock_instance.add_project_member.assert_any_call("proj1", "user1@example.com", ["Project member"])
254
+ mock_instance.add_project_member.assert_any_call("proj2", "user2@example.com", ["Project member", "Project administrator"])
255
+
256
+ @patch('os.path.exists', return_value=False)
257
+ def test_add_project_member_batch_file_not_found(self, mock_exists):
258
+ mock_client = Mock()
259
+
260
+ with self.assertRaises(MissingRequirementException) as context:
261
+ add_project_member_in_batch(mock_client, "nonexistent.csv")
262
+
263
+ self.assertIn("Batch file not found", str(context.exception))
264
+
265
+ @patch('pygeai.cli.commands.organization.Console.write_stdout')
266
+ @patch('builtins.open', new_callable=mock_open, read_data='proj1,user1@example.com\n')
267
+ @patch('os.path.exists', return_value=True)
268
+ def test_add_project_member_batch_invalid_format(self, mock_exists, mock_file, mock_write_stdout):
269
+ mock_client = Mock()
270
+ mock_client.add_project_member.return_value = {"status": "invitation sent"}
271
+
272
+ add_project_member_in_batch(mock_client, "test.csv")
273
+
274
+ calls = [call[0][0] for call in mock_write_stdout.call_args_list]
275
+ self.assertTrue(any("0 successful, 1 failed" in call for call in calls))
276
+ self.assertTrue(any("Invalid format" in call for call in calls))
@@ -70,7 +70,7 @@ class TestErrorHandler(TestCase):
70
70
  def test_format_error_basic(self):
71
71
  """Test basic error formatting"""
72
72
  result = ErrorHandler.format_error("Test Error", "Something went wrong")
73
- self.assertIn("ERROR: Something went wrong", result)
73
+ self.assertIn("ERROR [Test Error]: Something went wrong", result)
74
74
  self.assertIn("Run 'geai help' for usage information.", result)
75
75
 
76
76
  def test_format_error_with_suggestion(self):
@@ -80,7 +80,7 @@ class TestErrorHandler(TestCase):
80
80
  "Command not found",
81
81
  suggestion="Try using 'help' command"
82
82
  )
83
- self.assertIn("ERROR: Command not found", result)
83
+ self.assertIn("ERROR [Test Error]: Command not found", result)
84
84
  self.assertIn("→ Try using 'help' command", result)
85
85
 
86
86
  def test_format_error_without_help(self):
@@ -90,7 +90,7 @@ class TestErrorHandler(TestCase):
90
90
  "Critical error",
91
91
  show_help=False
92
92
  )
93
- self.assertIn("ERROR: Critical error", result)
93
+ self.assertIn("ERROR [Test Error]: Critical error", result)
94
94
  self.assertNotIn("Run 'geai help'", result)
95
95
 
96
96
  def test_find_similar_items_exact_match(self):
@@ -217,7 +217,7 @@ class TestErrorHandler(TestCase):
217
217
  ]
218
218
 
219
219
  for result in results:
220
- self.assertIn("ERROR:", result)
220
+ self.assertIn("ERROR [", result)
221
221
  self.assertIn("→", result)
222
222
 
223
223